You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by GitBox <gi...@apache.org> on 2018/07/20 01:52:41 UTC

[GitHub] darkma773r closed pull request #4: Points/Vectors as VALJOs

darkma773r closed pull request #4: Points/Vectors as VALJOs
URL: https://github.com/apache/commons-geometry/pull/4
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
index ad72eb7..c6f76c1 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
@@ -16,11 +16,9 @@
  */
 package org.apache.commons.geometry.core;
 
-import java.io.Serializable;
-
 /** Interface representing a generic element in a mathematical space.
  */
-public interface Spatial extends Serializable {
+public interface Spatial {
 
     /** Returns the number of dimensions in the space that this element
      * belongs to.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction1N.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction1N.java
new file mode 100644
index 0000000..6396bbb
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction1N.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+/** Represents a function that accepts a single double value and returns
+ * a result.
+ * @param <T> The function return type.
+ */
+@FunctionalInterface
+public interface DoubleFunction1N<T> {
+
+    /** Apply the function and return the result.
+     * @param n the function argument
+     * @return the function result
+     */
+    T apply(double n);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction2N.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction2N.java
new file mode 100644
index 0000000..36cbf7a
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction2N.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+/** Represents a function that accepts two double values and returns
+ * a result.
+ * @param <T> The function return type.
+ */
+@FunctionalInterface
+public interface DoubleFunction2N<T> {
+
+    /** Apply the function and return the result.
+     * @param n1 first function argument
+     * @param n2 second function argument
+     * @return the function result
+     */
+    T apply(double n1, double n2);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction3N.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction3N.java
new file mode 100644
index 0000000..086f003
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/DoubleFunction3N.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+/** Represents a function that accepts three double values and returns
+ * a result.
+ * @param <T> The function return type.
+ */
+@FunctionalInterface
+public interface DoubleFunction3N<T> {
+
+    /** Apply the function and return the result.
+     * @param n1 first function argument
+     * @param n2 second function argument
+     * @param n3 third function argument
+     * @return the function result
+     */
+    T apply(double n1, double n2, double n3);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
new file mode 100644
index 0000000..041380f
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+import java.text.ParsePosition;
+
+/** Class for performing simple formatting and parsing of real number tuples.
+ */
+public class SimpleTupleFormat {
+
+    /** Default value separator string. */
+    private static final String DEFAULT_SEPARATOR = ",";
+
+    /** Space character */
+    private static final String SPACE = " ";
+
+    /** Static instance configured with default values. Tuples in this format
+     * are enclosed by parentheses and separated by commas.
+     */
+    private static final SimpleTupleFormat DEFAULT_INSTANCE =
+            new SimpleTupleFormat(",", "(", ")");
+
+    /** String separating tuple values */
+    private final String separator;
+
+    /** String used to signal the start of a tuple; may be null */
+    private final String prefix;
+
+    /** String used to signal the end of a tuple; may be null */
+    private final String suffix;
+
+    /** Constructs a new instance with the default string separator (a comma)
+     * and the given prefix and suffix.
+     * @param prefix String used to signal the start of a tuple; if null, no
+     *      string is expected at the start of the tuple
+     * @param suffix String used to signal the end of a tuple; if null, no
+     *      string is expected at the end of the tuple
+     */
+    public SimpleTupleFormat(String prefix, String suffix) {
+        this(DEFAULT_SEPARATOR, prefix, suffix);
+    }
+
+    /** Simple constructor.
+     * @param separator String used to separate tuple values; must not be null.
+     * @param prefix String used to signal the start of a tuple; if null, no
+     *      string is expected at the start of the tuple
+     * @param suffix String used to signal the end of a tuple; if null, no
+     *      string is expected at the end of the tuple
+     */
+    protected SimpleTupleFormat(String separator, String prefix, String suffix) {
+        this.separator = separator;
+        this.prefix = prefix;
+        this.suffix = suffix;
+    }
+
+    /** Return the string used to separate tuple values.
+     * @return the value separator string
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    /** Return the string used to signal the start of a tuple. This value may be null.
+     * @return the string used to begin each tuple or null
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /** Returns the string used to signal the end of a tuple. This value may be null.
+     * @return the string used to end each tuple or null
+     */
+    public String getSuffix() {
+        return suffix;
+    }
+
+    /** Return a tuple string with the given value.
+     * @param a value
+     * @return 1-tuple string
+     */
+    public String format(double a) {
+        StringBuilder sb = new StringBuilder();
+
+        if (prefix != null) {
+            sb.append(prefix);
+        }
+
+        sb.append(a);
+
+        if (suffix != null) {
+            sb.append(suffix);
+        }
+
+        return sb.toString();
+    }
+
+    /** Return a tuple string with the given values.
+     * @param a1 first value
+     * @param a2 second value
+     * @return 2-tuple string
+     */
+    public String format(double a1, double a2) {
+        StringBuilder sb = new StringBuilder();
+
+        if (prefix != null) {
+            sb.append(prefix);
+        }
+
+        sb.append(a1);
+        sb.append(separator);
+        sb.append(SPACE);
+        sb.append(a2);
+
+        if (suffix != null) {
+            sb.append(suffix);
+        }
+
+        return sb.toString();
+    }
+
+    /** Return a tuple string with the given values.
+     * @param a1 first value
+     * @param a2 second value
+     * @param a3 third value
+     * @return 3-tuple string
+     */
+    public String format(double a1, double a2, double a3) {
+        StringBuilder sb = new StringBuilder();
+
+        if (prefix != null) {
+            sb.append(prefix);
+        }
+
+        sb.append(a1);
+        sb.append(separator);
+        sb.append(SPACE);
+        sb.append(a2);
+        sb.append(separator);
+        sb.append(SPACE);
+        sb.append(a3);
+
+        if (suffix != null) {
+            sb.append(suffix);
+        }
+
+        return sb.toString();
+    }
+
+    /** Parse the given string as a 1-tuple and passes the tuple values to the
+     * given function. The function output is returned.
+     * @param <T> function return type
+     * @param str the string to be parsed
+     * @param fn function that will be passed the parsed tuple values
+     * @return object returned by {@code fn}
+     * @throws IllegalArgumentException if the input string format is invalid
+     */
+    public <T> T parse(String str, DoubleFunction1N<T> fn) throws IllegalArgumentException {
+        final ParsePosition pos = new ParsePosition(0);
+
+        readPrefix(str, pos);
+        final double v = readTupleValue(str, pos);
+        readSuffix(str, pos);
+        endParse(str, pos);
+
+        return fn.apply(v);
+    }
+
+    /** Parse the given string as a 2-tuple and passes the tuple values to the
+     * given function. The function output is returned.
+     * @param <T> function return type
+     * @param str the string to be parsed
+     * @param fn function that will be passed the parsed tuple values
+     * @return object returned by {@code fn}
+     * @throws IllegalArgumentException if the input string format is invalid
+     */
+    public <T> T parse(String str, DoubleFunction2N<T> fn) throws IllegalArgumentException {
+        final ParsePosition pos = new ParsePosition(0);
+
+        readPrefix(str, pos);
+        final double v1 = readTupleValue(str, pos);
+        final double v2 = readTupleValue(str, pos);
+        readSuffix(str, pos);
+        endParse(str, pos);
+
+        return fn.apply(v1, v2);
+    }
+
+    /** Parse the given string as a 3-tuple and passes the parsed values to the
+     * given function. The function output is returned.
+     * @param <T> function return type
+     * @param str the string to be parsed
+     * @param fn function that will be passed the parsed tuple values
+     * @return object returned by {@code fn}
+     * @throws IllegalArgumentException if the input string format is invalid
+     */
+    public <T> T parse(String str, DoubleFunction3N<T> fn) throws IllegalArgumentException {
+        ParsePosition pos = new ParsePosition(0);
+
+        readPrefix(str, pos);
+        final double v1 = readTupleValue(str, pos);
+        final double v2 = readTupleValue(str, pos);
+        final double v3 = readTupleValue(str, pos);
+        readSuffix(str, pos);
+        endParse(str, pos);
+
+        return fn.apply(v1, v2, v3);
+    }
+
+    /** Read the configured prefix from the current position in the given string, ignoring any preceding
+     * whitespace, and advance the parsing position past the prefix sequence. An exception is thrown if the
+     * prefix is not found. Does nothing if the prefix is null.
+     * @param str the string being parsed
+     * @param pos the current parsing position
+     * @throws IllegalArgumentException if the configured prefix is not null and is not found at the current
+     *      parsing position, ignoring preceding whitespace
+     */
+    private void readPrefix(String str, ParsePosition pos) throws IllegalArgumentException {
+        if (prefix != null) {
+            consumeWhitespace(str, pos);
+            readSequence(str, prefix, pos);
+        }
+    }
+
+    /** Read and return a tuple value from the current position in the given string. An exception is thrown if a
+     * valid number is not found. The parsing position is advanced past the parsed number and any trailing separator.
+     * @param str the string being parsed
+     * @param pos the current parsing position
+     * @return the tuple value
+     * @throws IllegalArgumentException if the configured prefix is not null and is not found at the current
+     *      parsing position, ignoring preceding whitespace
+     */
+    private double readTupleValue(String str, ParsePosition pos) throws IllegalArgumentException {
+        final int startIdx = pos.getIndex();
+
+        int endIdx = str.indexOf(separator, startIdx);
+        if (endIdx < 0) {
+            if (suffix != null) {
+                endIdx = str.indexOf(suffix, startIdx);
+            }
+
+            if (endIdx < 0) {
+                endIdx = str.length();
+            }
+        }
+
+        String substr = str.substring(startIdx, endIdx);
+        try {
+            double value = Double.parseDouble(substr);
+
+            // advance the position and move past any terminating separator
+            pos.setIndex(endIdx);
+            matchSequence(str, separator, pos);
+
+            return value;
+        }
+        catch (NumberFormatException exc) {
+            fail(String.format("unable to parse number from string \"%s\"", substr), str, pos, exc);
+            return 0.0; // for the compiler
+        }
+    }
+
+    /** Read the configured suffix from the current position in the given string, ignoring any preceding
+     * whitespace, and advance the parsing position past the suffix sequence. An exception is thrown if the
+     * suffix is not found. Does nothing if the suffix is null.
+     * @param str the string being parsed
+     * @param pos the current parsing position
+     * @throws IllegalArgumentException if the configured suffix is not null and is not found at the current
+     *      parsing position, ignoring preceding whitespace
+     */
+    private void readSuffix(String str, ParsePosition pos) throws IllegalArgumentException {
+        if (suffix != null) {
+            consumeWhitespace(str, pos);
+            readSequence(str, suffix, pos);
+        }
+    }
+
+    /** End a parse operation by ensuring that all non-whitespace characters in the string have been parsed. An
+     * exception is thrown if extra content is found.
+     * @param str the string being parsed
+     * @param pos the current parsing position
+     * @throws IllegalArgumentException if extra non-whitespace content is found past the current parsing position
+     */
+    private void endParse(String str, ParsePosition pos) throws IllegalArgumentException {
+        consumeWhitespace(str, pos);
+        if (pos.getIndex() != str.length()) {
+            fail("unexpected content", str, pos);
+        }
+    }
+
+    /** Advance {@code pos} past any whitespace characters in {@code str},
+     * starting at the current parse position index.
+     * @param str the input string
+     * @param pos the current parse position
+     */
+    private void consumeWhitespace(final String str, final ParsePosition pos) {
+        int idx = pos.getIndex();
+        final int len = str.length();
+
+        for (; idx<len; ++idx) {
+            if (!Character.isWhitespace(str.codePointAt(idx))) {
+                break;
+            }
+        }
+
+        pos.setIndex(idx);
+    }
+
+    /** Return a boolean indicating whether or not the input string {@code str}
+     * contains the string {@code seq} at the given parse index. If the match succeeds,
+     * the index of {@code pos} is moved to the first character after the match. If
+     * the match does not succeed, the parse position is left unchanged.
+     * @param str the string to match against
+     * @param seq the sequence to look for in {@code str}
+     * @param pos the parse position indicating the index in {@code str}
+     *      to attempt the match
+     * @return true if {@code str} contains exactly the same characters as {@code seq}
+     *      at {@code pos}; otherwise, false
+     */
+    private boolean matchSequence(final String str, final String seq, final ParsePosition pos) {
+        final int idx = pos.getIndex();
+        final int inputLength = str.length();
+        final int seqLength = seq.length();
+
+        int i = idx;
+        int s = 0;
+        for (; i<inputLength && s<seqLength; ++i, ++s) {
+            if (str.codePointAt(i) != seq.codePointAt(s)) {
+                break;
+            }
+        }
+
+        if (i <= inputLength && s == seqLength) {
+            pos.setIndex(idx + seqLength);
+            return true;
+        }
+        return false;
+    }
+
+    /** Read the string given by {@code seq} from the given position in {@code str}.
+     * Throws an IllegalArgumentException if the sequence is not found at that position.
+     * @param str the string to match against
+     * @param seq the sequence to look for in {@code str}
+     * @param pos the parse position indicating the index in {@code str}
+     *      to attempt the match
+     * @throws IllegalArgumentException if {@code str} does not contain the characters from
+     *      {@code seq} at position {@code pos}
+     */
+    private void readSequence(String str, String seq, ParsePosition pos) throws IllegalArgumentException {
+        if (!matchSequence(str, seq, pos)) {
+            final int idx = pos.getIndex();
+            final String actualSeq = str.substring(idx, Math.min(str.length(), idx + seq.length()));
+
+            fail(String.format("expected \"%s\" but found \"%s\"", seq, actualSeq), str, pos);
+        }
+    }
+
+    /** Abort the current parsing operation by throwing an {@link IllegalArgumentException} with an informative
+     * error message.
+     * @param msg the error message
+     * @param str the string being parsed
+     * @param pos the current parse position
+     * @throws IllegalArgumentException the exception signaling a parse failure
+     */
+    private void fail(String msg, String str, ParsePosition pos) throws IllegalArgumentException {
+        fail(msg, str, pos, null);
+    }
+
+    /** Abort the current parsing operation by throwing an {@link IllegalArgumentException} with an informative
+     * error message.
+     * @param msg the error message
+     * @param str the string being parsed
+     * @param pos the current parse position
+     * @param cause the original cause of the error
+     * @throws IllegalArgumentException the exception signaling a parse failure
+     */
+    private void fail(String msg, String str, ParsePosition pos, Throwable cause) throws IllegalArgumentException {
+        String fullMsg = String.format("Failed to parse string \"%s\" at index %d: %s", str, pos.getIndex(), msg);
+
+        throw new TupleParseException(fullMsg, cause);
+    }
+
+    /** Return an instance configured with default values. Tuples in this format
+     * are enclosed by parentheses and separated by commas.
+     *
+     * Ex:
+     * <pre>
+     * "(1.0)"
+     * "(1.0, 2.0)"
+     * "(1.0, 2.0, 3.0)"
+     * </pre>
+     * @return instance configured with default values
+     */
+    public static SimpleTupleFormat getDefault() {
+        return DEFAULT_INSTANCE;
+    }
+
+    /** Exception class for errors occurring during tuple parsing.
+     */
+    private static class TupleParseException extends IllegalArgumentException {
+
+        /** Serializable version identifier */
+        private static final long serialVersionUID = 20180629;
+
+        /** Simple constructor.
+         * @param msg the exception message
+         * @param cause the exception root cause
+         */
+        TupleParseException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/package-info.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/package-info.java
new file mode 100644
index 0000000..1e4be0a
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ *
+ * <p>
+ * This package contains geometry utilities intended for internal use only.
+ * No guarantees are made for the stability of the contained APIs.
+ * </p>
+ */
+package org.apache.commons.geometry.core.internal;
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/SimpleTupleFormatTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/SimpleTupleFormatTest.java
new file mode 100644
index 0000000..6f1467d
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/SimpleTupleFormatTest.java
@@ -0,0 +1,465 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SimpleTupleFormatTest {
+
+    private static final double EPS = 1e-10;
+
+    private static final String OPEN_PAREN = "(";
+    private static final String CLOSE_PAREN = ")";
+
+    private static DoubleFunction1N<Stub1D> FACTORY_1D = new DoubleFunction1N<Stub1D>() {
+
+        @Override
+        public Stub1D apply(double v) {
+            Stub1D result = new Stub1D();
+            result.v = v;
+
+            return result;
+        }
+    };
+
+    private static DoubleFunction2N<Stub2D> FACTORY_2D = new DoubleFunction2N<Stub2D>() {
+
+        @Override
+        public Stub2D apply(double v1, double v2) {
+            Stub2D result = new Stub2D();
+            result.v1 = v1;
+            result.v2 = v2;
+
+            return result;
+        }
+    };
+
+    private static DoubleFunction3N<Stub3D> FACTORY_3D = new DoubleFunction3N<Stub3D>() {
+
+        @Override
+        public Stub3D apply(double v1, double v2, double v3) {
+            Stub3D result = new Stub3D();
+            result.v1 = v1;
+            result.v2 = v2;
+            result.v3 = v3;
+
+            return result;
+        }
+    };
+
+    @Test
+    public void testConstructor() {
+        // act
+        SimpleTupleFormat formatter = new SimpleTupleFormat("|", "{", "}");
+
+        // assert
+        Assert.assertEquals("|", formatter.getSeparator());
+        Assert.assertEquals("{", formatter.getPrefix());
+        Assert.assertEquals("}", formatter.getSuffix());
+    }
+
+    @Test
+    public void testConstructor_defaultSeparator() {
+        // act
+        SimpleTupleFormat formatter = new SimpleTupleFormat("{", "}");
+
+        // assert
+        Assert.assertEquals(",", formatter.getSeparator());
+        Assert.assertEquals("{", formatter.getPrefix());
+        Assert.assertEquals("}", formatter.getSuffix());
+    }
+
+    @Test
+    public void testFormat1D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        Assert.assertEquals("(1.0)", formatter.format(1.0));
+        Assert.assertEquals("(-1.0)", formatter.format(-1.0));
+        Assert.assertEquals("(NaN)", formatter.format(Double.NaN));
+        Assert.assertEquals("(-Infinity)", formatter.format(Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("(Infinity)", formatter.format(Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat1D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        Assert.assertEquals("1.0", formatter.format(1.0));
+        Assert.assertEquals("-1.0", formatter.format(-1.0));
+        Assert.assertEquals("NaN", formatter.format(Double.NaN));
+        Assert.assertEquals("-Infinity", formatter.format(Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("Infinity", formatter.format(Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat2D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        Assert.assertEquals("(1.0, -1.0)", formatter.format(1.0, -1.0));
+        Assert.assertEquals("(-1.0, 1.0)", formatter.format(-1.0, 1.0));
+        Assert.assertEquals("(NaN, -Infinity)", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("(-Infinity, Infinity)", formatter.format(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat2D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        Assert.assertEquals("1.0, -1.0", formatter.format(1.0, -1.0));
+        Assert.assertEquals("-1.0, 1.0", formatter.format(-1.0, 1.0));
+        Assert.assertEquals("NaN, -Infinity", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("-Infinity, Infinity", formatter.format(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat3D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        Assert.assertEquals("(1.0, 0.0, -1.0)", formatter.format(1.0, 0.0, -1.0));
+        Assert.assertEquals("(-1.0, 1.0, 0.0)", formatter.format(-1.0, 1.0, 0.0));
+        Assert.assertEquals("(NaN, -Infinity, Infinity)", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat3D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        Assert.assertEquals("1.0, 0.0, -1.0", formatter.format(1.0, 0.0, -1.0));
+        Assert.assertEquals("-1.0, 1.0, 0.0", formatter.format(-1.0, 1.0, 0.0));
+        Assert.assertEquals("NaN, -Infinity, Infinity", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+    }
+
+    @Test
+    public void testFormat_longTokens() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat("||", "<<", ">>");
+
+        // act/assert
+        Assert.assertEquals("<<1.0>>", formatter.format(1.0));
+        Assert.assertEquals("<<1.0|| 2.0>>", formatter.format(1.0, 2.0));
+        Assert.assertEquals("<<1.0|| 2.0|| 3.0>>", formatter.format(1.0, 2.0, 3.0));
+    }
+
+    @Test
+    public void testParse1D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse1D(formatter, "(1)", 1.0);
+        checkParse1D(formatter, "(-1)", -1.0);
+
+        checkParse1D(formatter, "(0.01)", 0.01);
+        checkParse1D(formatter, "(-1e-2)", -0.01);
+
+        checkParse1D(formatter, "(100)", 100);
+        checkParse1D(formatter, "(-1e2)", -100);
+
+        checkParse1D(formatter, " (\n 1 \t) ", 1);
+        checkParse1D(formatter, "\n ( -1 \t)\r\n", -1);
+
+        checkParse1D(formatter, "(1, )", 1.0);
+        checkParse1D(formatter, "(-1, )", -1.0);
+
+        checkParse1D(formatter, "(NaN)", Double.NaN);
+        checkParse1D(formatter, "(-Infinity)", Double.NEGATIVE_INFINITY);
+        checkParse1D(formatter, "(Infinity)", Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse1D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        checkParse1D(formatter, "1", 1.0);
+        checkParse1D(formatter, "-1", -1.0);
+
+        checkParse1D(formatter, "0.01", 0.01);
+        checkParse1D(formatter, "-1e-2", -0.01);
+
+        checkParse1D(formatter, "100", 100);
+        checkParse1D(formatter, "-1e2", -100);
+
+        checkParse1D(formatter, " \n 1 \t ", 1);
+        checkParse1D(formatter, "\n  -1 \t\r\n", -1);
+
+        checkParse1D(formatter, "1, ", 1.0);
+        checkParse1D(formatter, "-1, ", -1.0);
+
+        checkParse1D(formatter, "NaN", Double.NaN);
+        checkParse1D(formatter, "-Infinity", Double.NEGATIVE_INFINITY);
+        checkParse1D(formatter, "Infinity", Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse1D_failure() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse1DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse1DFailure(formatter, "(1 ", "index 3: expected \")\" but found \"\"");
+
+        checkParse1DFailure(formatter, "(abc)", "unable to parse number from string \"abc\"");
+
+        checkParse1DFailure(formatter, "(1) 1", "index 4: unexpected content");
+    }
+
+    @Test
+    public void testParse2D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse2D(formatter, "(1,-2)", 1.0, -2.0);
+        checkParse2D(formatter, "(2,-1)", 2.0, -1.0);
+
+        checkParse2D(formatter, "(0.01, -0.02)", 0.01, -0.02);
+        checkParse2D(formatter, "(-1e-2,2e-2)", -0.01, 0.02);
+
+        checkParse2D(formatter, "(100,  -1e2)", 100, -100);
+
+        checkParse2D(formatter, " (\n 1 , 2 \t) ", 1, 2);
+        checkParse2D(formatter, "\n ( -1 , -2 \t)\r\n", -1, -2);
+
+        checkParse2D(formatter, "(1, 2, )", 1.0, 2.0);
+        checkParse2D(formatter, "(-1, -2,)", -1.0, -2.0);
+
+        checkParse2D(formatter, "(NaN, -Infinity)", Double.NaN, Double.NEGATIVE_INFINITY);
+        checkParse2D(formatter, "(-Infinity, Infinity)", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse2D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        checkParse2D(formatter, "1,-2", 1.0, -2.0);
+        checkParse2D(formatter, "2,-1", 2.0, -1.0);
+
+        checkParse2D(formatter, "0.01, -0.02", 0.01, -0.02);
+        checkParse2D(formatter, "-1e-2,2e-2", -0.01, 0.02);
+
+        checkParse2D(formatter, "100,  -1e2", 100, -100);
+
+        checkParse2D(formatter, " \n 1 , 2 \t ", 1, 2);
+        checkParse2D(formatter, "\n  -1 , -2 \t\r\n", -1, -2);
+
+        checkParse2D(formatter, "1, 2, ", 1.0, 2.0);
+        checkParse2D(formatter, "-1, -2,", -1.0, -2.0);
+
+        checkParse2D(formatter, "NaN, -Infinity", Double.NaN, Double.NEGATIVE_INFINITY);
+        checkParse2D(formatter, "-Infinity, Infinity", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse2D_failure() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse2DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse2DFailure(formatter, "(1, 2 ", "index 6: expected \")\" but found \"\"");
+
+        checkParse2DFailure(formatter, "(0,abc)", "index 3: unable to parse number from string \"abc\"");
+
+        checkParse2DFailure(formatter, "(1, 2) 1", "index 7: unexpected content");
+    }
+
+    @Test
+    public void testParse3D() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse3D(formatter, "(1,-2,3)", 1.0, -2.0, 3.0);
+        checkParse3D(formatter, "(2,-1,3)", 2.0, -1.0, 3.0);
+
+        checkParse3D(formatter, "(0.01, -0.02, 0.3)", 0.01, -0.02, 0.3);
+        checkParse3D(formatter, "(-1e-2,2e-2,-3E-1)", -0.01, 0.02, -0.3);
+
+        checkParse3D(formatter, "(100,  -1e2,2E10)", 100, -100, 2e10);
+
+        checkParse3D(formatter, " (\n 1 , 2 , 3 \t) ", 1, 2, 3);
+        checkParse3D(formatter, "\n ( -1 , -2 ,  -3 \t)\r\n", -1, -2, -3);
+
+        checkParse3D(formatter, "(1, 2, 3, )", 1.0, 2.0, 3.0);
+        checkParse3D(formatter, "(-1, -2, -3,)", -1.0, -2.0, -3.0);
+
+        checkParse3D(formatter, "(NaN, -Infinity, Infinity)", Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse3D_noPrefixSuffix() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(null, null);
+
+        // act/assert
+        checkParse3D(formatter, "1,-2,3", 1.0, -2.0, 3.0);
+        checkParse3D(formatter, "2,-1,3", 2.0, -1.0, 3.0);
+
+        checkParse3D(formatter, "0.01, -0.02, 0.3", 0.01, -0.02, 0.3);
+        checkParse3D(formatter, "-1e-2,2e-2,-3E-1", -0.01, 0.02, -0.3);
+
+        checkParse3D(formatter, "100,  -1e2,2E10", 100, -100, 2e10);
+
+        checkParse3D(formatter, " \n 1 , 2 , 3 \t ", 1, 2, 3);
+        checkParse3D(formatter, "\n  -1 , -2 ,  -3 \t\r\n", -1, -2, -3);
+
+        checkParse3D(formatter, "1, 2, 3, ", 1.0, 2.0, 3.0);
+        checkParse3D(formatter, "-1, -2, -3,", -1.0, -2.0, -3.0);
+
+        checkParse3D(formatter, "NaN, -Infinity, Infinity", Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+    }
+
+    @Test
+    public void testParse3D_failure() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat(OPEN_PAREN, CLOSE_PAREN);
+
+        // act/assert
+        checkParse3DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse3DFailure(formatter, "(1, 2, 3", "index 8: expected \")\" but found \"\"");
+
+        checkParse3DFailure(formatter, "(0,0,abc)", "index 5: unable to parse number from string \"abc\"");
+
+        checkParse3DFailure(formatter, "(1, 2, 3) 1", "index 10: unexpected content");
+    }
+
+    @Test
+    public void testParse_longTokens() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat("||", "<<", ">>");
+
+        // act/assert
+        checkParse1D(formatter, "<<1.0>>", 1.0);
+        checkParse2D(formatter, "<<1.0|| 2.0>>", 1.0, 2.0);
+        checkParse3D(formatter, "<<1.0|| 2.0|| 3.0>>", 1.0, 2.0, 3.0);
+    }
+
+    @Test
+    public void testParse_longTokens_failure() {
+        // arrange
+        SimpleTupleFormat formatter = new SimpleTupleFormat("||", "<<", ">>");
+
+        // act/assert
+        checkParse1DFailure(formatter, "<", "index 0: expected \"<<\" but found \"<\"");
+        checkParse1DFailure(formatter, "<1.0>>", "index 0: expected \"<<\" but found \"<1\"");
+        checkParse2DFailure(formatter, "<<1.0| 2.0>>", "index 2: unable to parse number from string \"1.0| 2.0\"");
+        checkParse3DFailure(formatter, "<<1.0|| 2.0|| 3.0>", "index 13: unable to parse number from string \" 3.0>\"");
+    }
+
+    @Test
+    public void testDefaultInstance() {
+        // act
+        SimpleTupleFormat formatter = SimpleTupleFormat.getDefault();
+
+        // assert
+        Assert.assertEquals(",", formatter.getSeparator());
+        Assert.assertEquals("(", formatter.getPrefix());
+        Assert.assertEquals(")", formatter.getSuffix());
+
+        Assert.assertEquals("(1.0, 2.0)", formatter.format(1, 2));
+    }
+
+    private void checkParse1D(SimpleTupleFormat formatter, String str, double v) {
+        Stub1D result = formatter.parse(str, FACTORY_1D);
+
+        Assert.assertEquals(v, result.v, EPS);
+    }
+
+    private void checkParse1DFailure(SimpleTupleFormat formatter, String str, String msgSubstr) {
+        try {
+            formatter.parse(str, FACTORY_1D);
+            Assert.fail("Operation should have failed");
+        }
+        catch (IllegalArgumentException exc) {
+            String excMsg = exc.getMessage();
+            Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+                    excMsg.contains(msgSubstr));
+        }
+    }
+
+    private void checkParse2D(SimpleTupleFormat formatter, String str, double v1, double v2) {
+        Stub2D result = formatter.parse(str, FACTORY_2D);
+
+        Assert.assertEquals(v1, result.v1, EPS);
+        Assert.assertEquals(v2, result.v2, EPS);
+    }
+
+    private void checkParse2DFailure(SimpleTupleFormat formatter, String str, String msgSubstr) {
+        try {
+            formatter.parse(str, FACTORY_2D);
+            Assert.fail("Operation should have failed");
+        }
+        catch (IllegalArgumentException exc) {
+            String excMsg = exc.getMessage();
+            Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+                    excMsg.contains(msgSubstr));
+        }
+    }
+
+    private void checkParse3D(SimpleTupleFormat formatter, String str, double v1, double v2, double v3) {
+        Stub3D result = formatter.parse(str, FACTORY_3D);
+
+        Assert.assertEquals(v1, result.v1, EPS);
+        Assert.assertEquals(v2, result.v2, EPS);
+        Assert.assertEquals(v3, result.v3, EPS);
+    }
+
+    private void checkParse3DFailure(SimpleTupleFormat formatter, String str, String msgSubstr) {
+        try {
+            formatter.parse(str, FACTORY_3D);
+            Assert.fail("Operation should have failed");
+        }
+        catch (IllegalArgumentException exc) {
+            String excMsg = exc.getMessage();
+            Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+                    excMsg.contains(msgSubstr));
+        }
+    }
+
+    private static class Stub1D {
+        public double v;
+    }
+
+    private static class Stub2D {
+        public double v1;
+        public double v2;
+    }
+
+    private static class Stub3D {
+        public double v1;
+        public double v2;
+        public double v3;
+    }
+}
diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGenerator.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGenerator.java
index a124cf3..b0ada02 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGenerator.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGenerator.java
@@ -117,7 +117,7 @@
                         final BigFraction dy      = c3[0].subtract(centerY);
                         final BigFraction dz      = c4[0].subtract(centerZ);
                         final BigFraction r2      = dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz));
-                        return new EnclosingBall<>(new Point3D(centerX.doubleValue(),
+                        return new EnclosingBall<>(Point3D.of(centerX.doubleValue(),
                                                                                      centerY.doubleValue(),
                                                                                      centerZ.doubleValue()),
                                                                         Math.sqrt(r2.doubleValue()),
diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGenerator.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGenerator.java
index c2b9acc..bab99ac 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGenerator.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGenerator.java
@@ -85,7 +85,7 @@
                     final BigFraction dx      = c2[0].subtract(centerX);
                     final BigFraction dy      = c3[0].subtract(centerY);
                     final BigFraction r2      = dx.multiply(dx).add(dy.multiply(dy));
-                    return new EnclosingBall<>(new Point2D(centerX.doubleValue(),
+                    return new EnclosingBall<>(Point2D.of(centerX.doubleValue(),
                                                                                  centerY.doubleValue()),
                                                                     Math.sqrt(r2.doubleValue()),
                                                                     vA, vB, vC);
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser2DTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser2DTest.java
index 8a975e1..5f6fb80 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser2DTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser2DTest.java
@@ -88,7 +88,7 @@ public void testLargeSamples() {
             for (int i = 0; i < nbPoints; ++i) {
                 double x = random.nextDouble();
                 double y = random.nextDouble();
-                points.add(new Point2D(x, y));
+                points.add(Point2D.of(x, y));
             }
             checkDisk(points);
         }
@@ -97,7 +97,7 @@ public void testLargeSamples() {
     private List<Point2D> buildList(final double ... coordinates) {
         List<Point2D> list = new ArrayList<>(coordinates.length / 2);
         for (int i = 0; i < coordinates.length; i += 2) {
-            list.add(new Point2D(coordinates[i], coordinates[i + 1]));
+            list.add(Point2D.of(coordinates[i], coordinates[i + 1]));
         }
         return list;
     }
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
index e874ca7..b7fc5e0 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
@@ -53,17 +53,17 @@ public void testNoPoints() {
     @Test
     public void testReducingBall() {
         List<Point3D> list =
-                Arrays.asList(new Point3D(-7.140397329568118, -16.571661242582177,  11.714458961735405),
-                              new Point3D(-7.137986707455888, -16.570767323375720,  11.708602108715928),
-                              new Point3D(-7.139185068549035, -16.570891204702250,  11.715554057357394),
-                              new Point3D(-7.142682716997507, -16.571609818234290,  11.710787934580328),
-                              new Point3D(-7.139018392423351, -16.574405614157020,  11.710518716711425),
-                              new Point3D(-7.140870659936730, -16.567993074240455,  11.710914678204503),
-                              new Point3D(-7.136350173659562, -16.570498228820930,  11.713965225900928),
-                              new Point3D(-7.141675762759172, -16.572852471407028,  11.714033471449508),
-                              new Point3D(-7.140453077221105, -16.570212820780647,  11.708624578004980),
-                              new Point3D(-7.140322188726825, -16.574152894557717,  11.710305611121410),
-                              new Point3D(-7.141116131477088, -16.574061164624560,  11.712938509321699));
+                Arrays.asList(Point3D.of(-7.140397329568118, -16.571661242582177,  11.714458961735405),
+                              Point3D.of(-7.137986707455888, -16.570767323375720,  11.708602108715928),
+                              Point3D.of(-7.139185068549035, -16.570891204702250,  11.715554057357394),
+                              Point3D.of(-7.142682716997507, -16.571609818234290,  11.710787934580328),
+                              Point3D.of(-7.139018392423351, -16.574405614157020,  11.710518716711425),
+                              Point3D.of(-7.140870659936730, -16.567993074240455,  11.710914678204503),
+                              Point3D.of(-7.136350173659562, -16.570498228820930,  11.713965225900928),
+                              Point3D.of(-7.141675762759172, -16.572852471407028,  11.714033471449508),
+                              Point3D.of(-7.140453077221105, -16.570212820780647,  11.708624578004980),
+                              Point3D.of(-7.140322188726825, -16.574152894557717,  11.710305611121410),
+                              Point3D.of(-7.141116131477088, -16.574061164624560,  11.712938509321699));
         WelzlEncloser<Point3D> encloser =
                 new WelzlEncloser<>(1.0e-10, new SphereGenerator());
         EnclosingBall<Point3D> ball = encloser.enclose(list);
@@ -74,24 +74,24 @@ public void testReducingBall() {
     public void testInfiniteLoop() {
         // this test used to generate an infinite loop
         List<Point3D> list =
-                Arrays.asList(new Point3D( -0.89227075512164380,  -2.89317694645713900,  14.84572323743355500),
-                              new Point3D( -0.92099498940693580,  -2.31086108263908940,  12.92071026467688300),
-                              new Point3D( -0.85227999411005200,  -3.06314731441320730,  15.40163831651287000),
-                              new Point3D( -1.77399413020785970,  -3.65630391378114260,  14.13190097751873400),
-                              new Point3D(  0.33157833272465354,  -2.22813591757792160,  14.21225234159008200),
-                              new Point3D( -1.53065579165484400,  -1.65692084770139570,  14.61483055714788500),
-                              new Point3D( -1.08457093941217140,  -1.96100325935602980,  13.09265170575555000),
-                              new Point3D(  0.30029469589708850,  -3.05470831395667370,  14.56352400426342600),
-                              new Point3D( -0.95007443938638460,  -1.86810946486118360,  15.14491234340057000),
-                              new Point3D( -1.89661503804130830,  -2.17004080885185860,  14.81235128513927000),
-                              new Point3D( -0.72193328761607530,  -1.44513142833618270,  14.52355724218561800),
-                              new Point3D( -0.26895980939606550,  -3.69512371522084140,  14.72272846327652000),
-                              new Point3D( -1.53501693431786170,  -3.25055166611021900,  15.15509062584274800),
-                              new Point3D( -0.71727553535519410,  -3.62284279460799100,  13.26256700929380700),
-                              new Point3D( -0.30220950676137365,  -3.25410412500779070,  13.13682612771606000),
-                              new Point3D( -0.04543996608267075,  -1.93081853923797750,  14.79497997883171400),
-                              new Point3D( -1.53348892951571640,  -3.66688919703524900,  14.73095600812074200),
-                              new Point3D( -0.98034899533935820,  -3.34004481162763960,  13.03245014017556800));
+                Arrays.asList(Point3D.of( -0.89227075512164380,  -2.89317694645713900,  14.84572323743355500),
+                              Point3D.of( -0.92099498940693580,  -2.31086108263908940,  12.92071026467688300),
+                              Point3D.of( -0.85227999411005200,  -3.06314731441320730,  15.40163831651287000),
+                              Point3D.of( -1.77399413020785970,  -3.65630391378114260,  14.13190097751873400),
+                              Point3D.of(  0.33157833272465354,  -2.22813591757792160,  14.21225234159008200),
+                              Point3D.of( -1.53065579165484400,  -1.65692084770139570,  14.61483055714788500),
+                              Point3D.of( -1.08457093941217140,  -1.96100325935602980,  13.09265170575555000),
+                              Point3D.of(  0.30029469589708850,  -3.05470831395667370,  14.56352400426342600),
+                              Point3D.of( -0.95007443938638460,  -1.86810946486118360,  15.14491234340057000),
+                              Point3D.of( -1.89661503804130830,  -2.17004080885185860,  14.81235128513927000),
+                              Point3D.of( -0.72193328761607530,  -1.44513142833618270,  14.52355724218561800),
+                              Point3D.of( -0.26895980939606550,  -3.69512371522084140,  14.72272846327652000),
+                              Point3D.of( -1.53501693431786170,  -3.25055166611021900,  15.15509062584274800),
+                              Point3D.of( -0.71727553535519410,  -3.62284279460799100,  13.26256700929380700),
+                              Point3D.of( -0.30220950676137365,  -3.25410412500779070,  13.13682612771606000),
+                              Point3D.of( -0.04543996608267075,  -1.93081853923797750,  14.79497997883171400),
+                              Point3D.of( -1.53348892951571640,  -3.66688919703524900,  14.73095600812074200),
+                              Point3D.of( -0.98034899533935820,  -3.34004481162763960,  13.03245014017556800));
 
         WelzlEncloser<Point3D> encloser =
                 new WelzlEncloser<>(1.0e-10, new SphereGenerator());
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
index a1bf60b..b65d726 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
@@ -41,16 +41,16 @@ public void testSupport0Point() {
 
     @Test
     public void testSupport1Point() {
-        List<Point3D> support = Arrays.asList(new Point3D(1, 2, 3));
+        List<Point3D> support = Arrays.asList(Point3D.of(1, 2, 3));
         EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
         Assert.assertEquals(0.0, sphere.getRadius(), 1.0e-10);
         Assert.assertTrue(sphere.contains(support.get(0)));
         Assert.assertTrue(sphere.contains(support.get(0), 0.5));
-        Assert.assertFalse(sphere.contains(new Point3D(support.get(0).getX() + 0.1,
+        Assert.assertFalse(sphere.contains(Point3D.of(support.get(0).getX() + 0.1,
                                                         support.get(0).getY() + 0.1,
                                                         support.get(0).getZ() + 0.1),
                                            0.001));
-        Assert.assertTrue(sphere.contains(new Point3D(support.get(0).getX() + 0.1,
+        Assert.assertTrue(sphere.contains(Point3D.of(support.get(0).getX() + 0.1,
                                                        support.get(0).getY() + 0.1,
                                                        support.get(0).getZ() + 0.1),
                                           0.5));
@@ -61,8 +61,8 @@ public void testSupport1Point() {
 
     @Test
     public void testSupport2Points() {
-        List<Point3D> support = Arrays.asList(new Point3D(1, 0, 0),
-                                               new Point3D(3, 0, 0));
+        List<Point3D> support = Arrays.asList(Point3D.of(1, 0, 0),
+                                               Point3D.of(3, 0, 0));
         EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
         Assert.assertEquals(1.0, sphere.getRadius(), 1.0e-10);
         int i = 0;
@@ -71,17 +71,17 @@ public void testSupport2Points() {
             Assert.assertEquals(1.0, v.distance(sphere.getCenter()), 1.0e-10);
             Assert.assertTrue(v == sphere.getSupport()[i++]);
         }
-        Assert.assertTrue(sphere.contains(new Point3D(2, 0.9, 0)));
+        Assert.assertTrue(sphere.contains(Point3D.of(2, 0.9, 0)));
         Assert.assertFalse(sphere.contains(Point3D.ZERO));
-        Assert.assertEquals(0.0, new Point3D(2, 0, 0).distance(sphere.getCenter()), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(2, 0, 0).distance(sphere.getCenter()), 1.0e-10);
         Assert.assertEquals(2, sphere.getSupportSize());
     }
 
     @Test
     public void testSupport3Points() {
-        List<Point3D> support = Arrays.asList(new Point3D(1, 0, 0),
-                                               new Point3D(3, 0, 0),
-                                               new Point3D(2, 2, 0));
+        List<Point3D> support = Arrays.asList(Point3D.of(1, 0, 0),
+                                               Point3D.of(3, 0, 0),
+                                               Point3D.of(2, 2, 0));
         EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
         Assert.assertEquals(5.0 / 4.0, sphere.getRadius(), 1.0e-10);
         int i = 0;
@@ -90,23 +90,23 @@ public void testSupport3Points() {
             Assert.assertEquals(5.0 / 4.0, v.distance(sphere.getCenter()), 1.0e-10);
             Assert.assertTrue(v == sphere.getSupport()[i++]);
         }
-        Assert.assertTrue(sphere.contains(new Point3D(2, 0.9, 0)));
-        Assert.assertFalse(sphere.contains(new Point3D(0.9,  0, 0)));
-        Assert.assertFalse(sphere.contains(new Point3D(3.1,  0, 0)));
-        Assert.assertTrue(sphere.contains(new Point3D(2.0, -0.499, 0)));
-        Assert.assertFalse(sphere.contains(new Point3D(2.0, -0.501, 0)));
-        Assert.assertTrue(sphere.contains(new Point3D(2.0, 3.0 / 4.0, -1.249)));
-        Assert.assertFalse(sphere.contains(new Point3D(2.0, 3.0 / 4.0, -1.251)));
-        Assert.assertEquals(0.0, new Point3D(2.0, 3.0 / 4.0, 0).distance(sphere.getCenter()), 1.0e-10);
+        Assert.assertTrue(sphere.contains(Point3D.of(2, 0.9, 0)));
+        Assert.assertFalse(sphere.contains(Point3D.of(0.9,  0, 0)));
+        Assert.assertFalse(sphere.contains(Point3D.of(3.1,  0, 0)));
+        Assert.assertTrue(sphere.contains(Point3D.of(2.0, -0.499, 0)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2.0, -0.501, 0)));
+        Assert.assertTrue(sphere.contains(Point3D.of(2.0, 3.0 / 4.0, -1.249)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2.0, 3.0 / 4.0, -1.251)));
+        Assert.assertEquals(0.0, Point3D.of(2.0, 3.0 / 4.0, 0).distance(sphere.getCenter()), 1.0e-10);
         Assert.assertEquals(3, sphere.getSupportSize());
     }
 
     @Test
     public void testSupport4Points() {
-        List<Point3D> support = Arrays.asList(new Point3D(17, 14,  18),
-                                               new Point3D(11, 14,  22),
-                                               new Point3D( 2, 22,  17),
-                                               new Point3D(22, 11, -10));
+        List<Point3D> support = Arrays.asList(Point3D.of(17, 14,  18),
+                                               Point3D.of(11, 14,  22),
+                                               Point3D.of( 2, 22,  17),
+                                               Point3D.of(22, 11, -10));
         EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
         Assert.assertEquals(25.0, sphere.getRadius(), 1.0e-10);
         int i = 0;
@@ -115,19 +115,19 @@ public void testSupport4Points() {
             Assert.assertEquals(25.0, v.distance(sphere.getCenter()), 1.0e-10);
             Assert.assertTrue(v == sphere.getSupport()[i++]);
         }
-        Assert.assertTrue(sphere.contains (new Point3D(-22.999, 2, 2)));
-        Assert.assertFalse(sphere.contains(new Point3D(-23.001, 2, 2)));
-        Assert.assertTrue(sphere.contains (new Point3D( 26.999, 2, 2)));
-        Assert.assertFalse(sphere.contains(new Point3D( 27.001, 2, 2)));
-        Assert.assertTrue(sphere.contains (new Point3D(2, -22.999, 2)));
-        Assert.assertFalse(sphere.contains(new Point3D(2, -23.001, 2)));
-        Assert.assertTrue(sphere.contains (new Point3D(2,  26.999, 2)));
-        Assert.assertFalse(sphere.contains(new Point3D(2,  27.001, 2)));
-        Assert.assertTrue(sphere.contains (new Point3D(2, 2, -22.999)));
-        Assert.assertFalse(sphere.contains(new Point3D(2, 2, -23.001)));
-        Assert.assertTrue(sphere.contains (new Point3D(2, 2,  26.999)));
-        Assert.assertFalse(sphere.contains(new Point3D(2, 2,  27.001)));
-        Assert.assertEquals(0.0, new Point3D(2.0, 2.0, 2.0).distance(sphere.getCenter()), 1.0e-10);
+        Assert.assertTrue(sphere.contains (Point3D.of(-22.999, 2, 2)));
+        Assert.assertFalse(sphere.contains(Point3D.of(-23.001, 2, 2)));
+        Assert.assertTrue(sphere.contains (Point3D.of( 26.999, 2, 2)));
+        Assert.assertFalse(sphere.contains(Point3D.of( 27.001, 2, 2)));
+        Assert.assertTrue(sphere.contains (Point3D.of(2, -22.999, 2)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2, -23.001, 2)));
+        Assert.assertTrue(sphere.contains (Point3D.of(2,  26.999, 2)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2,  27.001, 2)));
+        Assert.assertTrue(sphere.contains (Point3D.of(2, 2, -22.999)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2, 2, -23.001)));
+        Assert.assertTrue(sphere.contains (Point3D.of(2, 2,  26.999)));
+        Assert.assertFalse(sphere.contains(Point3D.of(2, 2,  27.001)));
+        Assert.assertEquals(0.0, Point3D.of(2.0, 2.0, 2.0).distance(sphere.getCenter()), 1.0e-10);
         Assert.assertEquals(4, sphere.getSupportSize());
     }
 
@@ -153,16 +153,16 @@ public void testRandom() {
     @Test
     public void testDegeneratedCase() {
        final List<Point3D> support =
-               Arrays.asList(new Point3D(Math.scalb(-8039905610797991.0, -50),   //   -7.140870659936730
+               Arrays.asList(Point3D.of(Math.scalb(-8039905610797991.0, -50),   //   -7.140870659936730
                                           Math.scalb(-4663475464714142.0, -48),   //  -16.567993074240455
                                           Math.scalb( 6592658872616184.0, -49)),  //   11.710914678204503
-                             new Point3D(Math.scalb(-8036658568968473.0, -50),   //   -7.137986707455888
+                             Point3D.of(Math.scalb(-8036658568968473.0, -50),   //   -7.137986707455888
                                           Math.scalb(-4664256346424880.0, -48),   //  -16.570767323375720
                                           Math.scalb( 6591357011730307.0, -49)),  //  11.708602108715928)
-                             new Point3D(Math.scalb(-8037820142977230.0, -50),   //   -7.139018392423351
+                             Point3D.of(Math.scalb(-8037820142977230.0, -50),   //   -7.139018392423351
                                           Math.scalb(-4665280434237813.0, -48),   //  -16.574405614157020
                                           Math.scalb( 6592435966112099.0, -49)),  //   11.710518716711425
-                             new Point3D(Math.scalb(-8038007803611611.0, -50),   //   -7.139185068549035
+                             Point3D.of(Math.scalb(-8038007803611611.0, -50),   //   -7.139185068549035
                                           Math.scalb(-4664291215918380.0, -48),   //  -16.570891204702250
                                           Math.scalb( 6595270610894208.0, -49))); //   11.715554057357394
         EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
index fcafaa4..8770b6b 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
@@ -42,15 +42,15 @@ public void testSupport0Point() {
 
     @Test
     public void testSupport1Point() {
-        List<Point2D> support = Arrays.asList(new Point2D(1, 2));
+        List<Point2D> support = Arrays.asList(Point2D.of(1, 2));
         EnclosingBall<Point2D> disk = new DiskGenerator().ballOnSupport(support);
         Assert.assertEquals(0.0, disk.getRadius(), 1.0e-10);
         Assert.assertTrue(disk.contains(support.get(0)));
         Assert.assertTrue(disk.contains(support.get(0), 0.5));
-        Assert.assertFalse(disk.contains(new Point2D(support.get(0).getX() + 0.1,
+        Assert.assertFalse(disk.contains(Point2D.of(support.get(0).getX() + 0.1,
                                                       support.get(0).getY() - 0.1),
                                          0.001));
-        Assert.assertTrue(disk.contains(new Point2D(support.get(0).getX() + 0.1,
+        Assert.assertTrue(disk.contains(Point2D.of(support.get(0).getX() + 0.1,
                                                      support.get(0).getY() - 0.1),
                                         0.5));
         Assert.assertEquals(0, support.get(0).distance(disk.getCenter()), 1.0e-10);
@@ -60,8 +60,8 @@ public void testSupport1Point() {
 
     @Test
     public void testSupport2Points() {
-        List<Point2D> support = Arrays.asList(new Point2D(1, 0),
-                                               new Point2D(3, 0));
+        List<Point2D> support = Arrays.asList(Point2D.of(1, 0),
+                                               Point2D.of(3, 0));
         EnclosingBall<Point2D> disk = new DiskGenerator().ballOnSupport(support);
         Assert.assertEquals(1.0, disk.getRadius(), 1.0e-10);
         int i = 0;
@@ -70,17 +70,17 @@ public void testSupport2Points() {
             Assert.assertEquals(1.0, v.distance(disk.getCenter()), 1.0e-10);
             Assert.assertTrue(v == disk.getSupport()[i++]);
         }
-        Assert.assertTrue(disk.contains(new Point2D(2, 0.9)));
+        Assert.assertTrue(disk.contains(Point2D.of(2, 0.9)));
         Assert.assertFalse(disk.contains(Point2D.ZERO));
-        Assert.assertEquals(0.0, new Point2D(2, 0).distance(disk.getCenter()), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(2, 0).distance(disk.getCenter()), 1.0e-10);
         Assert.assertEquals(2, disk.getSupportSize());
     }
 
     @Test
     public void testSupport3Points() {
-        List<Point2D> support = Arrays.asList(new Point2D(1, 0),
-                                               new Point2D(3, 0),
-                                               new Point2D(2, 2));
+        List<Point2D> support = Arrays.asList(Point2D.of(1, 0),
+                                               Point2D.of(3, 0),
+                                               Point2D.of(2, 2));
         EnclosingBall<Point2D> disk = new DiskGenerator().ballOnSupport(support);
         Assert.assertEquals(5.0 / 4.0, disk.getRadius(), 1.0e-10);
         int i = 0;
@@ -89,12 +89,12 @@ public void testSupport3Points() {
             Assert.assertEquals(5.0 / 4.0, v.distance(disk.getCenter()), 1.0e-10);
             Assert.assertTrue(v == disk.getSupport()[i++]);
         }
-        Assert.assertTrue(disk.contains(new Point2D(2, 0.9)));
-        Assert.assertFalse(disk.contains(new Point2D(0.9,  0)));
-        Assert.assertFalse(disk.contains(new Point2D(3.1,  0)));
-        Assert.assertTrue(disk.contains(new Point2D(2.0, -0.499)));
-        Assert.assertFalse(disk.contains(new Point2D(2.0, -0.501)));
-        Assert.assertEquals(0.0, new Point2D(2.0, 3.0 / 4.0).distance(disk.getCenter()), 1.0e-10);
+        Assert.assertTrue(disk.contains(Point2D.of(2, 0.9)));
+        Assert.assertFalse(disk.contains(Point2D.of(0.9,  0)));
+        Assert.assertFalse(disk.contains(Point2D.of(3.1,  0)));
+        Assert.assertTrue(disk.contains(Point2D.of(2.0, -0.499)));
+        Assert.assertFalse(disk.contains(Point2D.of(2.0, -0.501)));
+        Assert.assertEquals(0.0, Point2D.of(2.0, 3.0 / 4.0).distance(disk.getCenter()), 1.0e-10);
         Assert.assertEquals(3, disk.getSupportSize());
     }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Cartesian1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Cartesian1D.java
index 53e2879..047a0ff 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Cartesian1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Cartesian1D.java
@@ -16,15 +16,18 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import java.io.Serializable;
+
 import org.apache.commons.geometry.core.Spatial;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 
 /** This class represents a Cartesian coordinate value in
  * one-dimensional Euclidean space.
  */
-public abstract class Cartesian1D implements Spatial {
+public abstract class Cartesian1D implements Spatial, Serializable {
 
     /** Serializable UID. */
-    private static final long serialVersionUID = -1178039568877797126L;
+    private static final long serialVersionUID = 20180710L;
 
     /** Abscissa (coordinate value). */
     private final double x;
@@ -62,4 +65,10 @@ public boolean isNaN() {
     public boolean isInfinite() {
         return !isNaN() && Double.isInfinite(x);
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleTupleFormat.getDefault().format(getX());
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/IntervalsSet.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/IntervalsSet.java
index 05e7798..376b8fc 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/IntervalsSet.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/IntervalsSet.java
@@ -105,14 +105,14 @@ public IntervalsSet(final Collection<SubHyperplane<Point1D>> boundary,
             }
             // the tree must be open on the negative infinity side
             final SubHyperplane<Point1D> upperCut =
-                new OrientedPoint(new Point1D(upper), true, tolerance).wholeHyperplane();
+                new OrientedPoint(Point1D.of(upper), true, tolerance).wholeHyperplane();
             return new BSPTree<>(upperCut,
                                new BSPTree<Point1D>(Boolean.FALSE),
                                new BSPTree<Point1D>(Boolean.TRUE),
                                null);
         }
         final SubHyperplane<Point1D> lowerCut =
-            new OrientedPoint(new Point1D(lower), false, tolerance).wholeHyperplane();
+            new OrientedPoint(Point1D.of(lower), false, tolerance).wholeHyperplane();
         if (Double.isInfinite(upper) && (upper > 0)) {
             // the tree must be open on the positive infinity side
             return new BSPTree<>(lowerCut,
@@ -123,7 +123,7 @@ public IntervalsSet(final Collection<SubHyperplane<Point1D>> boundary,
 
         // the tree must be bounded on the two sides
         final SubHyperplane<Point1D> upperCut =
-            new OrientedPoint(new Point1D(upper), true, tolerance).wholeHyperplane();
+            new OrientedPoint(Point1D.of(upper), true, tolerance).wholeHyperplane();
         return new BSPTree<>(lowerCut,
                                         new BSPTree<Point1D>(Boolean.FALSE),
                                         new BSPTree<>(upperCut,
@@ -157,7 +157,7 @@ protected void computeGeometricalProperties() {
             if (Double.isInfinite(size)) {
                 setBarycenter(Point1D.NaN);
             } else if (size > 0) {
-                setBarycenter(new Point1D(sum / size));
+                setBarycenter(Point1D.of(sum / size));
             } else {
                 setBarycenter(((OrientedPoint) getTree(false).getCut().getHyperplane()).getLocation());
             }
@@ -242,7 +242,7 @@ public double getSup() {
      * @return a new point for finite abscissa, null otherwise
      */
     private Point1D finiteOrNullPoint(final double x) {
-        return Double.isInfinite(x) ? null : new Point1D(x);
+        return Double.isInfinite(x) ? null : Point1D.of(x);
     }
 
     /** Build an ordered list of intervals representing the instance.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
index eeeed9f..acc12f8 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction1N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -44,13 +46,22 @@
         new Point1D(Double.NEGATIVE_INFINITY);
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 7556674948671647925L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction1N<Point1D> FACTORY = new DoubleFunction1N<Point1D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point1D apply(double n) {
+            return new Point1D(n);
+        }
+    };
 
     /** Simple constructor.
      * @param x abscissa (coordinate value)
-     * @see #getX()
      */
-    public Point1D(double x) {
+    private Point1D(double x) {
         super(x);
     }
 
@@ -134,12 +145,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "(" + getX() + ")";
-    }
-
     /** Returns a point with the given coordinate value.
      * @param x point coordinate
      * @return point instance
@@ -156,6 +161,16 @@ public static Point1D of(Cartesian1D value) {
         return new Point1D(value.getX());
     }
 
+    /** Parses the given string and returns a new point instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point1D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index 1e6ab7d..1a915f6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction1N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -44,12 +46,22 @@
         new Vector1D(Double.NEGATIVE_INFINITY);
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 1582116020164328846L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction1N<Vector1D> FACTORY = new DoubleFunction1N<Vector1D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector1D apply(double n) {
+            return new Vector1D(n);
+        }
+    };
 
     /** Simple constructor.
      * @param x abscissa (coordinate value)
      */
-    public Vector1D(double x) {
+    private Vector1D(double x) {
         super(x);
     }
 
@@ -217,12 +229,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "{" + getX() + "}";
-    }
-
     /** Returns a vector with the given coordinate value.
      * @param x vector coordinate
      * @return vector instance
@@ -239,6 +245,16 @@ public static Vector1D of(Cartesian1D value) {
         return new Vector1D(value.getX());
     }
 
+    /** Parses the given string and returns a new vector instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector1D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
index 4640b23..6dbe753 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
@@ -17,15 +17,18 @@
 
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.io.Serializable;
+
 import org.apache.commons.geometry.core.Spatial;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 
 /** This class represents a Cartesian coordinate value in
  * three-dimensional Euclidean space.
  */
-public abstract class Cartesian3D implements Spatial {
+public abstract class Cartesian3D implements Spatial, Serializable {
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 6249091865814886817L;
+    private static final long serialVersionUID = 20180710L;
 
     /** Abscissa (first coordinate value) */
     private final double x;
@@ -93,6 +96,12 @@ public boolean isInfinite() {
         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleTupleFormat.getDefault().format(getX(), getY(), getZ());
+    }
+
     /** Returns the Euclidean distance from this set of coordinates to the given coordinates.
      * @param other coordinates to compute the distance to.
      * @return Euclidean distance value
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line.java
index 68512a8..e7b4f38 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line.java
@@ -135,7 +135,7 @@ public Point3D pointAt(final double abscissa) {
      */
     @Override
     public Point1D toSubSpace(final Point3D point) {
-        return new Point1D(getAbscissa(point));
+        return Point1D.of(getAbscissa(point));
     }
 
     /** Transform a sub-space point into a space point.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/OutlineExtractor.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/OutlineExtractor.java
index 830d8bf..be504db 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/OutlineExtractor.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/OutlineExtractor.java
@@ -205,12 +205,12 @@ private void addContribution(final SubHyperplane<Point3D> facet, final boolean r
                     int previous         = closed ? (loop.length - 1) : 1;
                     Vector3D previous3D  = plane.toSpace(loop[previous]).asVector();
                     int current          = (previous + 1) % loop.length;
-                    Point2D pPoint       = new Point2D(previous3D.dotProduct(u),
+                    Point2D pPoint       = Point2D.of(previous3D.dotProduct(u),
                                                          previous3D.dotProduct(v));
                     while (current < loop.length) {
 
                         final Vector3D current3D = plane.toSpace(loop[current]).asVector();
-                        final Point2D  cPoint    = new Point2D(current3D.dotProduct(u),
+                        final Point2D  cPoint    = Point2D.of(current3D.dotProduct(u),
                                                                  current3D.dotProduct(v));
                         final org.apache.commons.geometry.euclidean.twod.Line line =
                             new org.apache.commons.geometry.euclidean.twod.Line(pPoint, cPoint, tolerance);
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index 5ac48dc..23fc6cd 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -241,7 +241,7 @@ public void revertSelf() {
     @Override
     public Point2D toSubSpace(final Point3D point) {
         Vector3D vec = point.asVector();
-        return new Point2D(vec.dotProduct(u), vec.dotProduct(v));
+        return Point2D.of(vec.dotProduct(u), vec.dotProduct(v));
     }
 
     /** Transform an in-plane point into a 3D space point.
@@ -380,7 +380,7 @@ public static Point3D intersection(final Plane plane1, final Plane plane2, final
         }
 
         final double r = 1.0 / determinant;
-        return new Point3D(
+        return Point3D.of(
                             (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r,
                             (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r,
                             (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r);
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
index 46de33d..7c2b3f7 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
@@ -17,6 +17,8 @@
 
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction3N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -42,7 +44,17 @@
         new Point3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
     /** Serializable version identifier. */
-    private static final long serialVersionUID = 1313493323784566947L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction3N<Point3D> FACTORY = new DoubleFunction3N<Point3D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point3D apply(double n1, double n2, double n3) {
+            return new Point3D(n1, n2, n3);
+        }
+    };
 
     /** Simple constructor.
      * Build a point from its coordinates
@@ -50,7 +62,7 @@
      * @param y ordinate
      * @param z height
      */
-    public Point3D(double x, double y, double z) {
+    private Point3D(double x, double y, double z) {
         super(x, y, z);
     }
 
@@ -69,7 +81,7 @@ public double distance(Point3D p) {
     /** {@inheritDoc} */
     @Override
     public Vector3D subtract(Point3D p) {
-        return new Vector3D(
+        return Vector3D.of(
                     getX() - p.getX(),
                     getY() - p.getY(),
                     getZ() - p.getZ()
@@ -142,12 +154,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "(" + getX() + "; " + getY() + "; " + getZ() + ")";
-    }
-
     /** Returns a point with the given coordinate values
      * @param x abscissa (first coordinate value)
      * @param y ordinate (second coordinate value)
@@ -178,6 +184,16 @@ public static Point3D of(double[] p) {
         return new Point3D(p[0], p[1], p[2]);
     }
 
+    /** Parses the given string and returns a new point instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point3D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
index d70efba..114eb51 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
@@ -152,12 +152,12 @@ public PolyhedronsSet(final double xMin, final double xMax,
             // too thin box, build an empty polygons set
             return new BSPTree<>(Boolean.FALSE);
         }
-        final Plane pxMin = new Plane(new Point3D(xMin, 0,    0),   Vector3D.MINUS_X, tolerance);
-        final Plane pxMax = new Plane(new Point3D(xMax, 0,    0),   Vector3D.PLUS_X,  tolerance);
-        final Plane pyMin = new Plane(new Point3D(0,    yMin, 0),   Vector3D.MINUS_Y, tolerance);
-        final Plane pyMax = new Plane(new Point3D(0,    yMax, 0),   Vector3D.PLUS_Y,  tolerance);
-        final Plane pzMin = new Plane(new Point3D(0,    0,   zMin), Vector3D.MINUS_Z, tolerance);
-        final Plane pzMax = new Plane(new Point3D(0,    0,   zMax), Vector3D.PLUS_Z,  tolerance);
+        final Plane pxMin = new Plane(Point3D.of(xMin, 0,    0),   Vector3D.MINUS_X, tolerance);
+        final Plane pxMax = new Plane(Point3D.of(xMax, 0,    0),   Vector3D.PLUS_X,  tolerance);
+        final Plane pyMin = new Plane(Point3D.of(0,    yMin, 0),   Vector3D.MINUS_Y, tolerance);
+        final Plane pyMax = new Plane(Point3D.of(0,    yMax, 0),   Vector3D.PLUS_Y,  tolerance);
+        final Plane pzMin = new Plane(Point3D.of(0,    0,   zMin), Vector3D.MINUS_Z, tolerance);
+        final Plane pzMax = new Plane(Point3D.of(0,    0,   zMax), Vector3D.PLUS_Z,  tolerance);
         final Region<Point3D> boundary =
         new RegionFactory<Point3D>().buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax);
         return boundary.getTree(false);
@@ -614,8 +614,8 @@ public Plane apply(final Hyperplane<Point3D> hyperplane) {
                 final Plane    oPlane = (Plane) original;
                 final Plane    tPlane = (Plane) transformed;
                 final Point3D p00    = oPlane.getOrigin();
-                final Point3D p10    = oPlane.toSpace(new Point2D(1.0, 0.0));
-                final Point3D p01    = oPlane.toSpace(new Point2D(0.0, 1.0));
+                final Point3D p10    = oPlane.toSpace(Point2D.of(1.0, 0.0));
+                final Point3D p01    = oPlane.toSpace(Point2D.of(0.0, 1.0));
                 final Point2D tP00   = tPlane.toSubSpace(apply(p00));
                 final Point2D tP10   = tPlane.toSubSpace(apply(p10));
                 final Point2D tP01   = tPlane.toSubSpace(apply(p01));
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
index 7261fd1..931be75 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
@@ -543,10 +543,10 @@ public Vector3D getAxis(final RotationConvention convention) {
         final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1;
         if (q0 < 0) {
             final double inverse = sgn / Math.sqrt(squaredSine);
-            return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse);
+            return Vector3D.of(q1 * inverse, q2 * inverse, q3 * inverse);
         }
         final double inverse = -sgn / Math.sqrt(squaredSine);
-        return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse);
+        return Vector3D.of(q1 * inverse, q2 * inverse, q3 * inverse);
     }
   }
 
@@ -1113,7 +1113,7 @@ public Vector3D applyTo(Vector3D u) {
 
     double s = q1 * x + q2 * y + q3 * z;
 
-    return new Vector3D(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x,
+    return Vector3D.of(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x,
                         2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y,
                         2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z);
 
@@ -1151,7 +1151,7 @@ public Vector3D applyInverseTo(Vector3D u) {
     double s = q1 * x + q2 * y + q3 * z;
     double m0 = -q0;
 
-    return new Vector3D(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x,
+    return Vector3D.of(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x,
                         2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y,
                         2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z);
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine.java
index fdf511b..d390536 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine.java
@@ -83,8 +83,8 @@ public SubLine(final Segment segment) throws IllegalArgumentException {
         final List<Segment> segments = new ArrayList<>(list.size());
 
         for (final Interval interval : list) {
-            final Point3D start = line.toSpace(new Point1D(interval.getInf()));
-            final Point3D end   = line.toSpace(new Point1D(interval.getSup()));
+            final Point3D start = line.toSpace(Point1D.of(interval.getInf()));
+            final Point3D end   = line.toSpace(Point1D.of(interval.getSup()));
             segments.add(new Segment(start, end, line));
         }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
index 5a9e0ac..a96dffd 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
@@ -16,13 +16,15 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction3N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in three-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
+public final class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
 
     /** Zero (null) vector (coordinates: 0, 0, 0). */
     public static final Vector3D ZERO   = new Vector3D(0, 0, 0);
@@ -59,18 +61,28 @@
         new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
     /** Serializable UID */
-    private static final long serialVersionUID = 3695385854431542858L;
+    private static final long serialVersionUID = 20180710L;
 
     /** Error message when norms are zero. */
     private static final String ZERO_NORM_MSG = "Norm is zero";
 
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction3N<Vector3D> FACTORY = new DoubleFunction3N<Vector3D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D apply(double n1, double n2, double n3) {
+            return new Vector3D(n1, n2, n3);
+        }
+    };
+
     /** Simple constructor.
      * Build a vector from its coordinates
      * @param x abscissa
      * @param y ordinate
      * @param z height
      */
-    public Vector3D(double x, double y, double z) {
+    private Vector3D(double x, double y, double z) {
         super(x, y, z);
     }
 
@@ -370,12 +382,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "{" + getX() + "; " + getY() + "; " + getZ() + "}";
-    }
-
     /** Computes the dot product between to vectors. This method simply
      * calls {@code v1.dotProduct(v2)}.
      * @param v1 first vector
@@ -458,6 +464,16 @@ public static Vector3D fromSpherical(double alpha, double delta) {
         return new Vector3D(x, y, z);
     }
 
+    /** Parses the given string and returns a new vector instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector3D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
index ba3b462..8d35fed 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
@@ -17,15 +17,18 @@
 
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.io.Serializable;
+
 import org.apache.commons.geometry.core.Spatial;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 
 /** This class represents a set of Cartesian coordinates in
  * two-dimensional Euclidean space.
  */
-public abstract class Cartesian2D implements Spatial {
+public abstract class Cartesian2D implements Spatial, Serializable {
 
     /** Serializable UID */
-    private static final long serialVersionUID = 2918583078965478552L;
+    private static final long serialVersionUID = 20180710L;
 
     /** Abscissa (first coordinate) */
     private final double x;
@@ -82,6 +85,12 @@ public boolean isInfinite() {
         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleTupleFormat.getDefault().format(getX(), getY());
+    }
+
     /** Returns the Euclidean distance from this value to the given value.
      * @param other the set of coordinates to compute the distance to
      * @return Euclidean distance
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
index cb3c2ea..c5a5394 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
@@ -212,14 +212,14 @@ public Line getReverse() {
     /** {@inheritDoc} */
     @Override
     public Point1D toSubSpace(final Point2D point) {
-        return new Point1D(LinearCombination.value(cos, point.getX(), sin, point.getY()));
+        return Point1D.of(LinearCombination.value(cos, point.getX(), sin, point.getY()));
     }
 
     /** {@inheritDoc} */
     @Override
     public Point2D toSpace(final Point1D point) {
         final double abscissa = point.getX();
-        return new Point2D(LinearCombination.value(abscissa, cos, -originOffset, sin),
+        return Point2D.of(LinearCombination.value(abscissa, cos, -originOffset, sin),
                             LinearCombination.value(abscissa, sin,  originOffset, cos));
     }
 
@@ -233,7 +233,7 @@ public Point2D intersection(final Line other) {
         if (Math.abs(d) < tolerance) {
             return null;
         }
-        return new Point2D(LinearCombination.value(cos, other.originOffset, -other.cos, originOffset) / d,
+        return Point2D.of(LinearCombination.value(cos, other.originOffset, -other.cos, originOffset) / d,
                             LinearCombination.value(sin, other.originOffset, -other.sin, originOffset) / d);
     }
 
@@ -301,7 +301,7 @@ public boolean sameOrientationAs(final Hyperplane<Point2D> other) {
     public Point2D getPointAt(final Point1D abscissa, final double offset) {
         final double x       = abscissa.getX();
         final double dOffset = offset - originOffset;
-        return new Point2D(LinearCombination.value(x, cos,  dOffset, sin),
+        return Point2D.of(LinearCombination.value(x, cos,  dOffset, sin),
                             LinearCombination.value(x, sin, -dOffset, cos));
     }
 
@@ -468,7 +468,7 @@ public void setOriginOffset(final double offset) {
         public Point2D apply(final Point2D point) {
             final double  x   = point.getX();
             final double  y   = point.getY();
-            return new Point2D(LinearCombination.value(cXX, x, cXY, y, cX1, 1),
+            return Point2D.of(LinearCombination.value(cXX, x, cXY, y, cX1, 1),
                                 LinearCombination.value(cYX, x, cYY, y, cY1, 1));
         }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
index e062b89..c9fde1f 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction2N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -41,21 +43,31 @@
         new Point2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 266938651998679754L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction2N<Point2D> FACTORY = new DoubleFunction2N<Point2D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point2D apply(double n1, double n2) {
+            return new Point2D(n1, n2);
+        }
+    };
 
     /** Simple constructor.
      * Build a point from its coordinates
      * @param x abscissa
      * @param y ordinate
      */
-    public Point2D(double x, double y) {
+    private Point2D(double x, double y) {
         super(x, y);
     }
 
     /** {@inheritDoc} */
     @Override
     public Vector2D asVector() {
-        return new Vector2D(getX(), getY());
+        return Vector2D.of(getX(), getY());
     }
 
     /** {@inheritDoc} */
@@ -131,12 +143,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "(" + getX() + "; " + getY() + ")";
-    }
-
     /** Returns a point with the given coordinate values
      * @param x abscissa (first coordinate value)
      * @param y ordinate (second coordinate value)
@@ -166,6 +172,16 @@ public static Point2D of(double[] p) {
         return new Point2D(p[0], p[1]);
     }
 
+    /** Parses the given string and returns a new point instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point2D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolygonsSet.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolygonsSet.java
index de79c32..e7ffbeb 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolygonsSet.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolygonsSet.java
@@ -158,10 +158,10 @@ public PolygonsSet(final double hyperplaneThickness, final Point2D ... vertices)
             // too thin box, build an empty polygons set
             return null;
         }
-        final Point2D minMin = new Point2D(xMin, yMin);
-        final Point2D minMax = new Point2D(xMin, yMax);
-        final Point2D maxMin = new Point2D(xMax, yMin);
-        final Point2D maxMax = new Point2D(xMax, yMax);
+        final Point2D minMin = Point2D.of(xMin, yMin);
+        final Point2D minMax = Point2D.of(xMin, yMax);
+        final Point2D maxMin = Point2D.of(xMax, yMin);
+        final Point2D maxMax = Point2D.of(xMax, yMax);
         return new Line[] {
             new Line(minMin, maxMin, tolerance),
             new Line(maxMin, maxMax, tolerance),
@@ -579,7 +579,7 @@ protected void computeGeometricalProperties() {
                 setBarycenter(Point2D.NaN);
             } else {
                 setSize(sum / 2);
-                setBarycenter(new Point2D(sumX / (3 * sum), sumY / (3 * sum)));
+                setBarycenter(Point2D.of(sumX / (3 * sum), sumY / (3 * sum)));
             }
 
         }
@@ -661,8 +661,8 @@ protected void computeGeometricalProperties() {
                         final Line line = loop.get(0).getLine();
                         vertices[i++] = new Point2D[] {
                             null,
-                            line.toSpace(new Point1D(-Float.MAX_VALUE)),
-                            line.toSpace(new Point1D(+Float.MAX_VALUE))
+                            line.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                            line.toSpace(Point1D.of(+Float.MAX_VALUE))
                         };
                     } else if (loop.get(0).getStart() == null) {
                         // open loop with at least one real point
@@ -675,7 +675,7 @@ protected void computeGeometricalProperties() {
                                 double x = segment.getLine().toSubSpace(segment.getEnd()).getX();
                                 x -= Math.max(1.0, Math.abs(x / 2));
                                 array[j++] = null;
-                                array[j++] = segment.getLine().toSpace(new Point1D(x));
+                                array[j++] = segment.getLine().toSpace(Point1D.of(x));
                             }
 
                             if (j < (array.length - 1)) {
@@ -685,7 +685,7 @@ protected void computeGeometricalProperties() {
                                 // last dummy point
                                 double x = segment.getLine().toSubSpace(segment.getStart()).getX();
                                 x += Math.max(1.0, Math.abs(x / 2));
-                                array[j++] = segment.getLine().toSpace(new Point1D(x));
+                                array[j++] = segment.getLine().toSpace(Point1D.of(x));
                             }
 
                         }
@@ -1047,9 +1047,9 @@ private void addContribution(final SubHyperplane<Point2D> sub,
 
                 // find the 2D points
                 final Point2D startV = Double.isInfinite(i.getInf()) ?
-                                        null : line.toSpace(new Point1D(i.getInf()));
+                                        null : line.toSpace(Point1D.of(i.getInf()));
                 final Point2D endV   = Double.isInfinite(i.getSup()) ?
-                                        null : line.toSpace(new Point1D(i.getSup()));
+                                        null : line.toSpace(Point1D.of(i.getSup()));
 
                 // recover the connectivity information
                 final BSPTree<Point2D> startN = selectClosest(startV, splitters);
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
index bc8b214..a728d61 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
@@ -100,7 +100,7 @@ public double distance(final Point2D p) {
             final double px = start.getX() + r * deltaX;
             final double py = start.getY() + r * deltaY;
 
-            final Point2D interPt = new Point2D(px, py);
+            final Point2D interPt = Point2D.of(px, py);
             return interPt.distance(p);
         }
     }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/SubLine.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/SubLine.java
index b9e63d6..94d2398 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/SubLine.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/SubLine.java
@@ -81,8 +81,8 @@ public SubLine(final Segment segment) {
         final List<Segment> segments = new ArrayList<>(list.size());
 
         for (final Interval interval : list) {
-            final Point2D start = line.toSpace(new Point1D(interval.getInf()));
-            final Point2D end   = line.toSpace(new Point1D(interval.getSup()));
+            final Point2D start = line.toSpace(Point1D.of(interval.getInf()));
+            final Point2D end   = line.toSpace(Point1D.of(interval.getSup()));
             segments.add(new Segment(start, end, line));
         }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
index 0bc84d2..e682b77 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.internal.DoubleFunction2N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -53,16 +55,26 @@
         new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
     /** Serializable UID */
-    private static final long serialVersionUID = 1746839897232305304L;
+    private static final long serialVersionUID = 20180710L;
 
     /** Error message when norms are zero. */
     private static final String ZERO_NORM_MSG = "Norm is zero";
 
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction2N<Vector2D> FACTORY = new DoubleFunction2N<Vector2D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector2D apply(double n1, double n2) {
+            return new Vector2D(n1, n2);
+        }
+    };
+
     /** Simple constructor.
      * @param x abscissa (first coordinate)
      * @param y ordinate (second coordinate)
      */
-    public Vector2D(double x, double y) {
+    private Vector2D(double x, double y) {
         super(x, y);
     }
 
@@ -77,7 +89,7 @@ public Vector2D(double x, double y) {
     /** {@inheritDoc} */
     @Override
     public Point2D asPoint() {
-        return new Point2D(getX(), getY());
+        return Point2D.of(getX(), getY());
     }
 
     /** {@inheritDoc} */
@@ -308,12 +320,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "{" + getX() + "; " + getY() + "}";
-    }
-
     /** Computes the dot product between to vectors. This method simply
      * calls {@code v1.dotProduct(v2)}.
      * @param v1 first vector
@@ -365,6 +371,16 @@ public static Vector2D of(double[] v) {
         return new Vector2D(v[0], v[1]);
     }
 
+    /** Parses the given string and returns a new vector instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector2D parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/core/partitioning/CharacterizationTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/core/partitioning/CharacterizationTest.java
index e2a94fb..dc28a43 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/core/partitioning/CharacterizationTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/core/partitioning/CharacterizationTest.java
@@ -38,7 +38,7 @@
     public void testCharacterize_insideLeaf() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        SubLine sub = buildSubLine(new Point2D(0, -1), new Point2D(0, 1));
+        SubLine sub = buildSubLine(Point2D.of(0, -1), Point2D.of(0, 1));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -57,7 +57,7 @@ public void testCharacterize_insideLeaf() {
     public void testCharacterize_outsideLeaf() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.FALSE);
-        SubLine sub = buildSubLine(new Point2D(0, -1), new Point2D(0, 1));
+        SubLine sub = buildSubLine(Point2D.of(0, -1), Point2D.of(0, 1));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -76,9 +76,9 @@ public void testCharacterize_outsideLeaf() {
     public void testCharacterize_onPlusSide() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(0, -1), new Point2D(0, -2));
+        SubLine sub = buildSubLine(Point2D.of(0, -1), Point2D.of(0, -2));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -97,9 +97,9 @@ public void testCharacterize_onPlusSide() {
     public void testCharacterize_onMinusSide() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(0, 1), new Point2D(0, 2));
+        SubLine sub = buildSubLine(Point2D.of(0, 1), Point2D.of(0, 2));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -118,9 +118,9 @@ public void testCharacterize_onMinusSide() {
     public void testCharacterize_onBothSides() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(0, -1), new Point2D(0, 1));
+        SubLine sub = buildSubLine(Point2D.of(0, -1), Point2D.of(0, 1));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -131,8 +131,8 @@ public void testCharacterize_onBothSides() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(0, 0), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 1), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, 0), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 1), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getInsideSplitters()));
         Iterator<BSPTree<Point2D>> insideSplitterIter = ch.getInsideSplitters().iterator();
@@ -143,8 +143,8 @@ public void testCharacterize_onBothSides() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(1, outside.getSegments().size());
-        assertVectorEquals(new Point2D(0, -1), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 0), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, -1), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 0), outside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getOutsideSplitters()));
         Iterator<BSPTree<Point2D>> outsideSplitterIter = ch.getOutsideSplitters().iterator();
@@ -155,10 +155,10 @@ public void testCharacterize_onBothSides() {
     public void testCharacterize_multipleSplits_reunitedOnPlusSide() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
-        cut(tree.getMinus(), buildLine(new Point2D(-1, 0), new Point2D(0, 1)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
+        cut(tree.getMinus(), buildLine(Point2D.of(-1, 0), Point2D.of(0, 1)));
 
-        SubLine sub = buildSubLine(new Point2D(0, -2), new Point2D(0, 2));
+        SubLine sub = buildSubLine(Point2D.of(0, -2), Point2D.of(0, 2));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -169,8 +169,8 @@ public void testCharacterize_multipleSplits_reunitedOnPlusSide() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(0, 1), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 2), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, 1), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 2), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(2, size(ch.getInsideSplitters()));
         Iterator<BSPTree<Point2D>> insideSplitterIter = ch.getInsideSplitters().iterator();
@@ -182,8 +182,8 @@ public void testCharacterize_multipleSplits_reunitedOnPlusSide() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(1, outside.getSegments().size());
-        assertVectorEquals(new Point2D(0, -2), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 1), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, -2), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 1), outside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(2, size(ch.getOutsideSplitters()));
         Iterator<BSPTree<Point2D>> outsideSplitterIter = ch.getOutsideSplitters().iterator();
@@ -195,11 +195,11 @@ public void testCharacterize_multipleSplits_reunitedOnPlusSide() {
     public void testCharacterize_multipleSplits_reunitedOnMinusSide() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
-        cut(tree.getMinus(), buildLine(new Point2D(-1, 0), new Point2D(0, 1)));
-        cut(tree.getMinus().getPlus(), buildLine(new Point2D(-0.5, 0.5), new Point2D(0, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
+        cut(tree.getMinus(), buildLine(Point2D.of(-1, 0), Point2D.of(0, 1)));
+        cut(tree.getMinus().getPlus(), buildLine(Point2D.of(-0.5, 0.5), Point2D.of(0, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(0, -2), new Point2D(0, 2));
+        SubLine sub = buildSubLine(Point2D.of(0, -2), Point2D.of(0, 2));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -210,8 +210,8 @@ public void testCharacterize_multipleSplits_reunitedOnMinusSide() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(0, 0), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 2), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, 0), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 2), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(2, size(ch.getInsideSplitters()));
         Iterator<BSPTree<Point2D>> insideSplitterIter = ch.getInsideSplitters().iterator();
@@ -223,8 +223,8 @@ public void testCharacterize_multipleSplits_reunitedOnMinusSide() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(1, outside.getSegments().size());
-        assertVectorEquals(new Point2D(0, -2), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 0), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, -2), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 0), outside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getOutsideSplitters()));
         Iterator<BSPTree<Point2D>> outsideSplitterIter = ch.getOutsideSplitters().iterator();
@@ -235,9 +235,9 @@ public void testCharacterize_multipleSplits_reunitedOnMinusSide() {
     public void testCharacterize_onHyperplane_sameOrientation() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(0, 0), new Point2D(1, 0));
+        SubLine sub = buildSubLine(Point2D.of(0, 0), Point2D.of(1, 0));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -256,9 +256,9 @@ public void testCharacterize_onHyperplane_sameOrientation() {
     public void testCharacterize_onHyperplane_oppositeOrientation() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
 
-        SubLine sub = buildSubLine(new Point2D(1, 0), new Point2D(0, 0));
+        SubLine sub = buildSubLine(Point2D.of(1, 0), Point2D.of(0, 0));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -277,10 +277,10 @@ public void testCharacterize_onHyperplane_oppositeOrientation() {
     public void testCharacterize_onHyperplane_multipleSplits_sameOrientation() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
-        cut(tree.getMinus(), buildLine(new Point2D(-1, 0), new Point2D(0, 1)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
+        cut(tree.getMinus(), buildLine(Point2D.of(-1, 0), Point2D.of(0, 1)));
 
-        SubLine sub = buildSubLine(new Point2D(-2, 0), new Point2D(2, 0));
+        SubLine sub = buildSubLine(Point2D.of(-2, 0), Point2D.of(2, 0));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -291,8 +291,8 @@ public void testCharacterize_onHyperplane_multipleSplits_sameOrientation() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(-2, 0), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(-1, 0), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(-2, 0), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(-1, 0), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getInsideSplitters()));
         Iterator<BSPTree<Point2D>> insideSplitterIter = ch.getInsideSplitters().iterator();
@@ -303,8 +303,8 @@ public void testCharacterize_onHyperplane_multipleSplits_sameOrientation() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(1, outside.getSegments().size());
-        assertVectorEquals(new Point2D(-1, 0), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(2, 0), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(-1, 0), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(2, 0), outside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getOutsideSplitters()));
         Iterator<BSPTree<Point2D>> outsideSplitterIter = ch.getOutsideSplitters().iterator();
@@ -315,10 +315,10 @@ public void testCharacterize_onHyperplane_multipleSplits_sameOrientation() {
     public void testCharacterize_onHyperplane_multipleSplits_oppositeOrientation() {
         // arrange
         BSPTree<Point2D> tree = new BSPTree<>(Boolean.TRUE);
-        cut(tree, buildLine(new Point2D(0, 0), new Point2D(1, 0)));
-        cut(tree.getMinus(), buildLine(new Point2D(-1, 0), new Point2D(0, 1)));
+        cut(tree, buildLine(Point2D.of(0, 0), Point2D.of(1, 0)));
+        cut(tree.getMinus(), buildLine(Point2D.of(-1, 0), Point2D.of(0, 1)));
 
-        SubLine sub = buildSubLine(new Point2D(2, 0), new Point2D(-2, 0));
+        SubLine sub = buildSubLine(Point2D.of(2, 0), Point2D.of(-2, 0));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -329,8 +329,8 @@ public void testCharacterize_onHyperplane_multipleSplits_oppositeOrientation() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(-1, 0), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(-2, 0), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(-1, 0), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(-2, 0), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getInsideSplitters()));
         Iterator<BSPTree<Point2D>> insideSplitterIter = ch.getInsideSplitters().iterator();
@@ -341,8 +341,8 @@ public void testCharacterize_onHyperplane_multipleSplits_oppositeOrientation() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(1, outside.getSegments().size());
-        assertVectorEquals(new Point2D(2, 0), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(-1, 0), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(2, 0), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(-1, 0), outside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(1, size(ch.getOutsideSplitters()));
         Iterator<BSPTree<Point2D>> outsideSplitterIter = ch.getOutsideSplitters().iterator();
@@ -355,7 +355,7 @@ public void testCharacterize_onHyperplane_box() {
         PolygonsSet poly = new PolygonsSet(0, 1, 0, 1, TEST_TOLERANCE);
         BSPTree<Point2D> tree = poly.getTree(false);
 
-        SubLine sub = buildSubLine(new Point2D(2, 0), new Point2D(-2, 0));
+        SubLine sub = buildSubLine(Point2D.of(2, 0), Point2D.of(-2, 0));
 
         // act
         Characterization<Point2D> ch = new Characterization<>(tree, sub);
@@ -366,8 +366,8 @@ public void testCharacterize_onHyperplane_box() {
 
         SubLine inside = (SubLine) ch.insideTouching();
         Assert.assertEquals(1, inside.getSegments().size());
-        assertVectorEquals(new Point2D(1, 0), inside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(0, 0), inside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(1, 0), inside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(0, 0), inside.getSegments().get(0).getEnd());
 
         Assert.assertEquals(2, size(ch.getInsideSplitters()));
 
@@ -376,10 +376,10 @@ public void testCharacterize_onHyperplane_box() {
 
         SubLine outside = (SubLine) ch.outsideTouching();
         Assert.assertEquals(2, outside.getSegments().size());
-        assertVectorEquals(new Point2D(2, 0), outside.getSegments().get(0).getStart());
-        assertVectorEquals(new Point2D(1, 0), outside.getSegments().get(0).getEnd());
-        assertVectorEquals(new Point2D(0, 0), outside.getSegments().get(1).getStart());
-        assertVectorEquals(new Point2D(-2, 0), outside.getSegments().get(1).getEnd());
+        assertVectorEquals(Point2D.of(2, 0), outside.getSegments().get(0).getStart());
+        assertVectorEquals(Point2D.of(1, 0), outside.getSegments().get(0).getEnd());
+        assertVectorEquals(Point2D.of(0, 0), outside.getSegments().get(1).getStart());
+        assertVectorEquals(Point2D.of(-2, 0), outside.getSegments().get(1).getEnd());
 
         Assert.assertEquals(2, size(ch.getOutsideSplitters()));
     }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/EuclideanTestUtils.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/EuclideanTestUtils.java
index 9fb0df1..e3ebdf9 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/EuclideanTestUtils.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/EuclideanTestUtils.java
@@ -181,7 +181,7 @@ public static IntervalsSet parseIntervalsSet(final String s)
             @Override
             public OrientedPoint parseHyperplane()
                 throws IOException, ParseException {
-                return new OrientedPoint(new Point1D(getNumber()), getBoolean(), getNumber());
+                return new OrientedPoint(Point1D.of(getNumber()), getBoolean(), getNumber());
             }
 
         };
@@ -202,7 +202,7 @@ public static PolygonsSet parsePolygonsSet(final String s)
             @Override
             public Line parseHyperplane()
                 throws IOException, ParseException {
-                return new Line(new Point2D(getNumber(), getNumber()), getNumber(), getNumber());
+                return new Line(Point2D.of(getNumber(), getNumber()), getNumber(), getNumber());
             }
 
         };
@@ -223,8 +223,8 @@ public static PolyhedronsSet parsePolyhedronsSet(final String s)
             @Override
             public Plane parseHyperplane()
                 throws IOException, ParseException {
-                return new Plane(new Point3D(getNumber(), getNumber(), getNumber()),
-                                 new Vector3D(getNumber(), getNumber(), getNumber()),
+                return new Plane(Point3D.of(getNumber(), getNumber(), getNumber()),
+                                 Vector3D.of(getNumber(), getNumber(), getNumber()),
                                  getNumber());
             }
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Cartesian1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Cartesian1DTest.java
index 835a368..afd6ca8 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Cartesian1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Cartesian1DTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import java.util.regex.Pattern;
+
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -64,6 +66,20 @@ public void testInfinite() {
         Assert.assertFalse(new StubCartesian1D(Double.NaN).isInfinite());
     }
 
+    @Test
+    public void testToString() {
+        // arrange
+        StubCartesian1D c = new StubCartesian1D(1);
+        Pattern pattern = Pattern.compile("\\(1.{0,2}\\)");
+
+        // act
+        String str = c.toString();
+
+        // assert
+        Assert.assertTrue("Expected string " + str + " to match regex " + pattern,
+                    pattern.matcher(str).matches());
+    }
+
     private static class StubCartesian1D extends Cartesian1D {
         private static final long serialVersionUID = 1L;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalsSetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalsSetTest.java
index dd70adf..f032f7b 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalsSetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalsSetTest.java
@@ -150,7 +150,7 @@ public void testInterval_singleClosedInterval() {
         Assert.assertEquals(9.0, set.getSup(), TEST_TOLERANCE);
         Assert.assertEquals(10.0, set.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(0.0, set.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point1D(4.0), set.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point1D.of(4.0), set.getBarycenter(), TEST_TOLERANCE);
 
         List<Interval> intervals = set.asList();
         Assert.assertEquals(1, intervals.size());
@@ -176,7 +176,7 @@ public void testInterval_singlePoint() {
         Assert.assertEquals(1.0, set.getSup(), TEST_TOLERANCE);
         Assert.assertEquals(0.0, set.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(0.0, set.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point1D(1.0), set.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point1D.of(1.0), set.getBarycenter(), TEST_TOLERANCE);
 
         List<Interval> intervals = set.asList();
         Assert.assertEquals(1, intervals.size());
@@ -294,7 +294,7 @@ public void testFromBoundaries_singleClosedInterval() {
         Assert.assertEquals(9.0, set.getSup(), TEST_TOLERANCE);
         Assert.assertEquals(10.0, set.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(0.0, set.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point1D(4.0), set.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point1D.of(4.0), set.getBarycenter(), TEST_TOLERANCE);
 
         List<Interval> intervals = set.asList();
         Assert.assertEquals(1, intervals.size());
@@ -327,7 +327,7 @@ public void testFromBoundaries_multipleClosedIntervals() {
         Assert.assertEquals(9.0, set.getSup(), TEST_TOLERANCE);
         Assert.assertEquals(7.0, set.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(0.0, set.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point1D(29.5 / 7.0), set.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point1D.of(29.5 / 7.0), set.getBarycenter(), TEST_TOLERANCE);
 
         List<Interval> intervals = set.asList();
         Assert.assertEquals(2, intervals.size());
@@ -365,7 +365,7 @@ public void testFromBoundaries_mixedOpenAndClosedIntervals() {
         EuclideanTestUtils.assertPositiveInfinity(set.getSup());
         EuclideanTestUtils.assertPositiveInfinity(set.getSize());
         Assert.assertEquals(0.0, set.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point1D(Double.NaN), set.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point1D.of(Double.NaN), set.getBarycenter(), TEST_TOLERANCE);
 
         List<Interval> intervals = set.asList();
         Assert.assertEquals(4, intervals.size());
@@ -461,31 +461,31 @@ public void testProjectToBoundary() {
         IntervalsSet set = new IntervalsSet(boundaries, TEST_TOLERANCE);
 
         // act/assert
-        assertProjection(new Point1D(-2), -1, set, new Point1D(-3));
-        assertProjection(new Point1D(-2), 0, set, new Point1D(-2));
-        assertProjection(new Point1D(-2), 0.1, set, new Point1D(-1.9));
+        assertProjection(Point1D.of(-2), -1, set, Point1D.of(-3));
+        assertProjection(Point1D.of(-2), 0, set, Point1D.of(-2));
+        assertProjection(Point1D.of(-2), 0.1, set, Point1D.of(-1.9));
 
-        assertProjection(new Point1D(-1), 0.5, set, new Point1D(-1.5));
-        assertProjection(new Point1D(-1), 0.1, set, new Point1D(-1.1));
-        assertProjection(new Point1D(-1), 0, set, new Point1D(-1));
-        assertProjection(new Point1D(-1), -1, set, new Point1D(0));
+        assertProjection(Point1D.of(-1), 0.5, set, Point1D.of(-1.5));
+        assertProjection(Point1D.of(-1), 0.1, set, Point1D.of(-1.1));
+        assertProjection(Point1D.of(-1), 0, set, Point1D.of(-1));
+        assertProjection(Point1D.of(-1), -1, set, Point1D.of(0));
 
-        assertProjection(new Point1D(2), -1, set, new Point1D(1));
-        assertProjection(new Point1D(2), 0, set, new Point1D(2));
-        assertProjection(new Point1D(2), 1, set, new Point1D(3));
+        assertProjection(Point1D.of(2), -1, set, Point1D.of(1));
+        assertProjection(Point1D.of(2), 0, set, Point1D.of(2));
+        assertProjection(Point1D.of(2), 1, set, Point1D.of(3));
 
-        assertProjection(new Point1D(5), 1, set, new Point1D(4));
-        assertProjection(new Point1D(5), 0, set, new Point1D(5));
+        assertProjection(Point1D.of(5), 1, set, Point1D.of(4));
+        assertProjection(Point1D.of(5), 0, set, Point1D.of(5));
 
-        assertProjection(new Point1D(5), -1, set, new Point1D(6));
-        assertProjection(new Point1D(5), -2, set, new Point1D(7));
+        assertProjection(Point1D.of(5), -1, set, Point1D.of(6));
+        assertProjection(Point1D.of(5), -2, set, Point1D.of(7));
 
-        assertProjection(new Point1D(9), -1, set, new Point1D(8));
-        assertProjection(new Point1D(9), 0, set, new Point1D(9));
-        assertProjection(new Point1D(9), 0.1, set, new Point1D(9.1));
+        assertProjection(Point1D.of(9), -1, set, Point1D.of(8));
+        assertProjection(Point1D.of(9), 0, set, Point1D.of(9));
+        assertProjection(Point1D.of(9), 0.1, set, Point1D.of(9.1));
 
-        assertProjection(new Point1D(10), 0, set, new Point1D(10));
-        assertProjection(new Point1D(10), -1, set, new Point1D(11));
+        assertProjection(Point1D.of(10), 0, set, Point1D.of(10));
+        assertProjection(Point1D.of(10), -1, set, Point1D.of(11));
     }
 
     @Test
@@ -493,11 +493,11 @@ public void testInterval() {
         IntervalsSet set = new IntervalsSet(2.3, 5.7, 1.0e-10);
         Assert.assertEquals(3.4, set.getSize(), 1.0e-10);
         Assert.assertEquals(4.0, set.getBarycenter().getX(), 1.0e-10);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new Point1D(2.3)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new Point1D(5.7)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new Point1D(1.2)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new Point1D(8.7)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new Point1D(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(Point1D.of(2.3)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(Point1D.of(5.7)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(Point1D.of(1.2)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(Point1D.of(8.7)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(Point1D.of(3.0)));
         Assert.assertEquals(2.3, set.getInf(), 1.0e-10);
         Assert.assertEquals(5.7, set.getSup(), 1.0e-10);
     }
@@ -505,11 +505,11 @@ public void testInterval() {
     @Test
     public void testInfinite() {
         IntervalsSet set = new IntervalsSet(9.0, Double.POSITIVE_INFINITY, 1.0e-10);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new Point1D(9.0)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new Point1D(8.4)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(Point1D.of(9.0)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(Point1D.of(8.4)));
         for (double e = 1.0; e <= 6.0; e += 1.0) {
             Assert.assertEquals(Region.Location.INSIDE,
-                                set.checkPoint(new Point1D(Math.pow(10.0, e))));
+                                set.checkPoint(Point1D.of(Math.pow(10.0, e))));
         }
         Assert.assertTrue(Double.isInfinite(set.getSize()));
         Assert.assertEquals(9.0, set.getInf(), 1.0e-10);
@@ -558,7 +558,7 @@ public void testBooleanOperations() {
     }
 
     private void assertLocation(Region.Location location, IntervalsSet set, double pt) {
-        Assert.assertEquals(location, set.checkPoint(new Point1D(pt)));
+        Assert.assertEquals(location, set.checkPoint(Point1D.of(pt)));
     }
 
     private void assertInterval(double expectedInf, double expectedSup, Interval actual, double tolerance) {
@@ -581,6 +581,6 @@ private SubOrientedPoint subOrientedPoint(double location, boolean direct) {
 
     private SubOrientedPoint subOrientedPoint(double location, boolean direct, double tolerance) {
         // the remaining region isn't necessary for creating 1D boundaries so we can set it to null here
-        return new SubOrientedPoint(new OrientedPoint(new Point1D(location), direct, tolerance), null);
+        return new SubOrientedPoint(new OrientedPoint(Point1D.of(location), direct, tolerance), null);
     }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
index a986b91..37140ca 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
@@ -26,7 +26,7 @@
     @Test
     public void testConstructor() {
         // act
-        OrientedPoint pt = new OrientedPoint(new Point1D(2.0), true, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(2.0), true, 1e-5);
 
         // assert
         Assert.assertEquals(2.0, pt.getLocation().getX(), Precision.EPSILON);
@@ -37,7 +37,7 @@ public void testConstructor() {
     @Test
     public void testCopySelf() {
         // arrange
-        OrientedPoint orig = new OrientedPoint(new Point1D(2.0), true, 1e-5);
+        OrientedPoint orig = new OrientedPoint(Point1D.of(2.0), true, 1e-5);
 
         // act
         OrientedPoint copy = orig.copySelf();
@@ -52,37 +52,37 @@ public void testCopySelf() {
     @Test
     public void testGetOffset_direct_point() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(-1.0), true, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(-1.0), true, 1e-5);
 
         // act/assert
-        Assert.assertEquals(-99, pt.getOffset(new Point1D(-100)), Precision.EPSILON);
-        Assert.assertEquals(-1, pt.getOffset(new Point1D(-2)), Precision.EPSILON);
-        Assert.assertEquals(-0.01, pt.getOffset(new Point1D(-1.01)), Precision.EPSILON);
-        Assert.assertEquals(0.0, pt.getOffset(new Point1D(-1.0)), Precision.EPSILON);
-        Assert.assertEquals(0.01, pt.getOffset(new Point1D(-0.99)), Precision.EPSILON);
-        Assert.assertEquals(1, pt.getOffset(new Point1D(0)), Precision.EPSILON);
-        Assert.assertEquals(101, pt.getOffset(new Point1D(100)), Precision.EPSILON);
+        Assert.assertEquals(-99, pt.getOffset(Point1D.of(-100)), Precision.EPSILON);
+        Assert.assertEquals(-1, pt.getOffset(Point1D.of(-2)), Precision.EPSILON);
+        Assert.assertEquals(-0.01, pt.getOffset(Point1D.of(-1.01)), Precision.EPSILON);
+        Assert.assertEquals(0.0, pt.getOffset(Point1D.of(-1.0)), Precision.EPSILON);
+        Assert.assertEquals(0.01, pt.getOffset(Point1D.of(-0.99)), Precision.EPSILON);
+        Assert.assertEquals(1, pt.getOffset(Point1D.of(0)), Precision.EPSILON);
+        Assert.assertEquals(101, pt.getOffset(Point1D.of(100)), Precision.EPSILON);
     }
 
     @Test
     public void testGetOffset_notDirect_point() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(-1.0), false, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(-1.0), false, 1e-5);
 
         // act/assert
-        Assert.assertEquals(99, pt.getOffset(new Point1D(-100)), Precision.EPSILON);
-        Assert.assertEquals(1, pt.getOffset(new Point1D(-2)), Precision.EPSILON);
-        Assert.assertEquals(0.01, pt.getOffset(new Point1D(-1.01)), Precision.EPSILON);
-        Assert.assertEquals(0.0, pt.getOffset(new Point1D(-1.0)), Precision.EPSILON);
-        Assert.assertEquals(-0.01, pt.getOffset(new Point1D(-0.99)), Precision.EPSILON);
-        Assert.assertEquals(-1, pt.getOffset(new Point1D(0)), Precision.EPSILON);
-        Assert.assertEquals(-101, pt.getOffset(new Point1D(100)), Precision.EPSILON);
+        Assert.assertEquals(99, pt.getOffset(Point1D.of(-100)), Precision.EPSILON);
+        Assert.assertEquals(1, pt.getOffset(Point1D.of(-2)), Precision.EPSILON);
+        Assert.assertEquals(0.01, pt.getOffset(Point1D.of(-1.01)), Precision.EPSILON);
+        Assert.assertEquals(0.0, pt.getOffset(Point1D.of(-1.0)), Precision.EPSILON);
+        Assert.assertEquals(-0.01, pt.getOffset(Point1D.of(-0.99)), Precision.EPSILON);
+        Assert.assertEquals(-1, pt.getOffset(Point1D.of(0)), Precision.EPSILON);
+        Assert.assertEquals(-101, pt.getOffset(Point1D.of(100)), Precision.EPSILON);
     }
 
     @Test
     public void testWholeHyperplane() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(1.0), false, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(1.0), false, 1e-5);
 
         // act
         SubOrientedPoint subPt = pt.wholeHyperplane();
@@ -95,7 +95,7 @@ public void testWholeHyperplane() {
     @Test
     public void testWholeSpace() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(1.0), false, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(1.0), false, 1e-5);
 
         // act
         IntervalsSet set = pt.wholeSpace();
@@ -108,10 +108,10 @@ public void testWholeSpace() {
     @Test
     public void testSameOrientationAs() {
         // arrange
-        OrientedPoint notDirect1 = new OrientedPoint(new Point1D(1.0), false, 1e-5);
-        OrientedPoint notDirect2 = new OrientedPoint(new Point1D(1.0), false, 1e-5);
-        OrientedPoint direct1 = new OrientedPoint(new Point1D(1.0), true, 1e-5);
-        OrientedPoint direct2 = new OrientedPoint(new Point1D(1.0), true, 1e-5);
+        OrientedPoint notDirect1 = new OrientedPoint(Point1D.of(1.0), false, 1e-5);
+        OrientedPoint notDirect2 = new OrientedPoint(Point1D.of(1.0), false, 1e-5);
+        OrientedPoint direct1 = new OrientedPoint(Point1D.of(1.0), true, 1e-5);
+        OrientedPoint direct2 = new OrientedPoint(Point1D.of(1.0), true, 1e-5);
 
         // act/assert
         Assert.assertTrue(notDirect1.sameOrientationAs(notDirect1));
@@ -129,19 +129,19 @@ public void testSameOrientationAs() {
     @Test
     public void testProject() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(1.0), true, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(1.0), true, 1e-5);
 
         // act/assert
-        Assert.assertEquals(1.0, pt.project(new Point1D(-1.0)).getX(), Precision.EPSILON);
-        Assert.assertEquals(1.0, pt.project(new Point1D(0.0)).getX(), Precision.EPSILON);
-        Assert.assertEquals(1.0, pt.project(new Point1D(1.0)).getX(), Precision.EPSILON);
-        Assert.assertEquals(1.0, pt.project(new Point1D(100.0)).getX(), Precision.EPSILON);
+        Assert.assertEquals(1.0, pt.project(Point1D.of(-1.0)).getX(), Precision.EPSILON);
+        Assert.assertEquals(1.0, pt.project(Point1D.of(0.0)).getX(), Precision.EPSILON);
+        Assert.assertEquals(1.0, pt.project(Point1D.of(1.0)).getX(), Precision.EPSILON);
+        Assert.assertEquals(1.0, pt.project(Point1D.of(100.0)).getX(), Precision.EPSILON);
     }
 
     @Test
     public void testRevertSelf() {
         // arrange
-        OrientedPoint pt = new OrientedPoint(new Point1D(2.0), true, 1e-5);
+        OrientedPoint pt = new OrientedPoint(Point1D.of(2.0), true, 1e-5);
 
         // act
         pt.revertSelf();
@@ -151,7 +151,7 @@ public void testRevertSelf() {
         Assert.assertFalse(pt.isDirect());
         Assert.assertEquals(1e-5, pt.getTolerance(), Precision.EPSILON);
 
-        Assert.assertEquals(1, pt.getOffset(new Point1D(1.0)), Precision.EPSILON);
-        Assert.assertEquals(-1, pt.getOffset(new Point1D(3.0)), Precision.EPSILON);
+        Assert.assertEquals(1, pt.getOffset(Point1D.of(1.0)), Precision.EPSILON);
+        Assert.assertEquals(-1, pt.getOffset(Point1D.of(3.0)), Precision.EPSILON);
     }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
index 702abef..4b65983 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
@@ -182,6 +182,27 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point1D.parse("(1)"), 1);
+        checkPoint(Point1D.parse("(-1)"), -1);
+
+        checkPoint(Point1D.parse("(0.01)"), 1e-2);
+        checkPoint(Point1D.parse("(-1e-3)"), -1e-3);
+
+        checkPoint(Point1D.parse("(NaN)"), Double.NaN);
+
+        checkPoint(Point1D.parse(Point1D.ZERO.toString()), 0);
+        checkPoint(Point1D.parse(Point1D.ONE.toString()), 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point1D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
@@ -197,13 +218,13 @@ public void testOf() {
     @Test
     public void testOf_coordinateArg() {
         // act/assert
-        checkPoint(Point1D.of(new Vector1D(0)), 0.0);
-        checkPoint(Point1D.of(new Vector1D(-1)), -1.0);
-        checkPoint(Point1D.of(new Vector1D(1)), 1.0);
-        checkPoint(Point1D.of(new Vector1D(Math.PI)), Math.PI);
-        checkPoint(Point1D.of(new Vector1D(Double.NaN)), Double.NaN);
-        checkPoint(Point1D.of(new Vector1D(Double.NEGATIVE_INFINITY)), Double.NEGATIVE_INFINITY);
-        checkPoint(Point1D.of(new Vector1D(Double.POSITIVE_INFINITY)), Double.POSITIVE_INFINITY);
+        checkPoint(Point1D.of(Vector1D.of(0)), 0.0);
+        checkPoint(Point1D.of(Vector1D.of(-1)), -1.0);
+        checkPoint(Point1D.of(Vector1D.of(1)), 1.0);
+        checkPoint(Point1D.of(Vector1D.of(Math.PI)), Math.PI);
+        checkPoint(Point1D.of(Vector1D.of(Double.NaN)), Double.NaN);
+        checkPoint(Point1D.of(Vector1D.of(Double.NEGATIVE_INFINITY)), Double.NEGATIVE_INFINITY);
+        checkPoint(Point1D.of(Vector1D.of(Double.POSITIVE_INFINITY)), Double.POSITIVE_INFINITY);
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPointTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPointTest.java
index 6cf8de7..ab3c9d9 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPointTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPointTest.java
@@ -28,7 +28,7 @@
     @Test
     public void testGetSize() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         SubOrientedPoint pt = hyperplane.wholeHyperplane();
 
         // act/assert
@@ -38,7 +38,7 @@ public void testGetSize() {
     @Test
     public void testIsEmpty() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         SubOrientedPoint pt = hyperplane.wholeHyperplane();
 
         // act/assert
@@ -48,10 +48,10 @@ public void testIsEmpty() {
     @Test
     public void testBuildNew() {
         // arrange
-        OrientedPoint originalHyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint originalHyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         SubOrientedPoint pt = originalHyperplane.wholeHyperplane();
 
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(2), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(2), true, TEST_TOLERANCE);
         IntervalsSet intervals = new IntervalsSet(2, 3, TEST_TOLERANCE);
 
         // act
@@ -66,11 +66,11 @@ public void testBuildNew() {
     @Test
     public void testSplit_resultOnMinusSide() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         IntervalsSet interval = new IntervalsSet(TEST_TOLERANCE);
         SubOrientedPoint pt = new SubOrientedPoint(hyperplane, interval);
 
-        OrientedPoint splitter = new OrientedPoint(new Point1D(2), true, TEST_TOLERANCE);
+        OrientedPoint splitter = new OrientedPoint(Point1D.of(2), true, TEST_TOLERANCE);
 
         // act
         SplitSubHyperplane<Point1D> split = pt.split(splitter);
@@ -92,11 +92,11 @@ public void testSplit_resultOnMinusSide() {
     @Test
     public void testSplit_resultOnPlusSide() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         IntervalsSet interval = new IntervalsSet(TEST_TOLERANCE);
         SubOrientedPoint pt = new SubOrientedPoint(hyperplane, interval);
 
-        OrientedPoint splitter = new OrientedPoint(new Point1D(0), true, TEST_TOLERANCE);
+        OrientedPoint splitter = new OrientedPoint(Point1D.of(0), true, TEST_TOLERANCE);
 
         // act
         SplitSubHyperplane<Point1D> split = pt.split(splitter);
@@ -118,11 +118,11 @@ public void testSplit_resultOnPlusSide() {
     @Test
     public void testSplit_equivalentHyperplanes() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
         IntervalsSet interval = new IntervalsSet(TEST_TOLERANCE);
         SubOrientedPoint pt = new SubOrientedPoint(hyperplane, interval);
 
-        OrientedPoint splitter = new OrientedPoint(new Point1D(1), true, TEST_TOLERANCE);
+        OrientedPoint splitter = new OrientedPoint(Point1D.of(1), true, TEST_TOLERANCE);
 
         // act
         SplitSubHyperplane<Point1D> split = pt.split(splitter);
@@ -137,23 +137,23 @@ public void testSplit_equivalentHyperplanes() {
     @Test
     public void testSplit_usesToleranceFromParentHyperplane() {
         // arrange
-        OrientedPoint hyperplane = new OrientedPoint(new Point1D(1), true, 0.1);
+        OrientedPoint hyperplane = new OrientedPoint(Point1D.of(1), true, 0.1);
         SubOrientedPoint pt = hyperplane.wholeHyperplane();
 
         // act/assert
-        SplitSubHyperplane<Point1D> plusSplit = pt.split(new OrientedPoint(new Point1D(0.899), true, 1e-10));
+        SplitSubHyperplane<Point1D> plusSplit = pt.split(new OrientedPoint(Point1D.of(0.899), true, 1e-10));
         Assert.assertNull(plusSplit.getMinus());
         Assert.assertNotNull(plusSplit.getPlus());
 
-        SplitSubHyperplane<Point1D> lowWithinTolerance = pt.split(new OrientedPoint(new Point1D(0.901), true, 1e-10));
+        SplitSubHyperplane<Point1D> lowWithinTolerance = pt.split(new OrientedPoint(Point1D.of(0.901), true, 1e-10));
         Assert.assertNull(lowWithinTolerance.getMinus());
         Assert.assertNull(lowWithinTolerance.getPlus());
 
-        SplitSubHyperplane<Point1D> highWithinTolerance = pt.split(new OrientedPoint(new Point1D(1.09), true, 1e-10));
+        SplitSubHyperplane<Point1D> highWithinTolerance = pt.split(new OrientedPoint(Point1D.of(1.09), true, 1e-10));
         Assert.assertNull(highWithinTolerance.getMinus());
         Assert.assertNull(highWithinTolerance.getPlus());
 
-        SplitSubHyperplane<Point1D> minusSplit = pt.split(new OrientedPoint(new Point1D(1.101), true, 1e-10));
+        SplitSubHyperplane<Point1D> minusSplit = pt.split(new OrientedPoint(Point1D.of(1.101), true, 1e-10));
         Assert.assertNotNull(minusSplit.getMinus());
         Assert.assertNull(minusSplit.getPlus());
     }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
index fa757ab..15c864f 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
@@ -280,7 +280,7 @@ public void testEquals() {
     public void testToString() {
         // arrange
         Vector1D v = Vector1D.of(3);
-        Pattern pattern = Pattern.compile("\\{3.{0,2}\\}");
+        Pattern pattern = Pattern.compile("\\(3.{0,2}\\)");
 
         // act
         String str = v.toString();
@@ -290,6 +290,27 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector1D.parse("(1)"), 1);
+        checkVector(Vector1D.parse("(-1)"), -1);
+
+        checkVector(Vector1D.parse("(0.01)"), 1e-2);
+        checkVector(Vector1D.parse("(-1e-3)"), -1e-3);
+
+        checkVector(Vector1D.parse("(NaN)"), Double.NaN);
+
+        checkVector(Vector1D.parse(Vector1D.ZERO.toString()), 0);
+        checkVector(Vector1D.parse(Vector1D.ONE.toString()), 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector1D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java
index 4e6205d..aadfe73 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java
@@ -1,5 +1,7 @@
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.regex.Pattern;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -73,6 +75,20 @@ public void testInfinite() {
         Assert.assertFalse(new StubCartesian3D(0, Double.NaN, Double.POSITIVE_INFINITY).isInfinite());
     }
 
+    @Test
+    public void testToString() {
+        // arrange
+        StubCartesian3D c = new StubCartesian3D(1, 2, 3);
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)");
+
+        // act
+        String str = c.toString();
+
+        // assert
+        Assert.assertTrue("Expected string " + str + " to match regex " + pattern,
+                    pattern.matcher(str).matches());
+    }
+
     private static class StubCartesian3D extends Cartesian3D {
         private static final long serialVersionUID = 1L;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/LineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/LineTest.java
index 6a6a87b..e49c61b 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/LineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/LineTest.java
@@ -23,8 +23,8 @@
 
     @Test
     public void testContains() {
-        Point3D p1 = new Point3D(0, 0, 1);
-        Line l = new Line(p1, new Point3D(0, 0, 2), 1.0e-10);
+        Point3D p1 = Point3D.of(0, 0, 1);
+        Line l = new Line(p1, Point3D.of(0, 0, 2), 1.0e-10);
         Assert.assertTrue(l.contains(p1));
         Assert.assertTrue(l.contains(Point3D.vectorCombination(1.0, p1, 0.3, l.getDirection())));
         Vector3D u = l.getDirection().orthogonal();
@@ -37,8 +37,8 @@ public void testContains() {
 
     @Test
     public void testSimilar() {
-        Point3D p1  = new Point3D (1.2, 3.4, -5.8);
-        Point3D p2  = new Point3D (3.4, -5.8, 1.2);
+        Point3D p1  = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2  = Point3D.of(3.4, -5.8, 1.2);
         Line     lA  = new Line(p1, p2, 1.0e-10);
         Line     lB  = new Line(p2, p1, 1.0e-10);
         Assert.assertTrue(lA.isSimilarTo(lB));
@@ -47,89 +47,89 @@ public void testSimilar() {
 
     @Test
     public void testPointDistance() {
-        Line l = new Line(new Point3D(0, 1, 1), new Point3D(0, 2, 2), 1.0e-10);
-        Assert.assertEquals(Math.sqrt(3.0 / 2.0), l.distance(new Point3D(1, 0, 1)), 1.0e-10);
-        Assert.assertEquals(0, l.distance(new Point3D(0, -4, -4)), 1.0e-10);
+        Line l = new Line(Point3D.of(0, 1, 1), Point3D.of(0, 2, 2), 1.0e-10);
+        Assert.assertEquals(Math.sqrt(3.0 / 2.0), l.distance(Point3D.of(1, 0, 1)), 1.0e-10);
+        Assert.assertEquals(0, l.distance(Point3D.of(0, -4, -4)), 1.0e-10);
     }
 
     @Test
     public void testLineDistance() {
-        Line l = new Line(new Point3D(0, 1, 1), new Point3D(0, 2, 2), 1.0e-10);
+        Line l = new Line(Point3D.of(0, 1, 1), Point3D.of(0, 2, 2), 1.0e-10);
         Assert.assertEquals(1.0,
-                            l.distance(new Line(new Point3D(1, 0, 1), new Point3D(1, 0, 2), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(1, 0, 1), Point3D.of(1, 0, 2), 1.0e-10)),
                             1.0e-10);
         Assert.assertEquals(0.5,
-                            l.distance(new Line(new Point3D(-0.5, 0, 0), new Point3D(-0.5, -1, -1), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(-0.5, 0, 0), Point3D.of(-0.5, -1, -1), 1.0e-10)),
                             1.0e-10);
         Assert.assertEquals(0.0,
                             l.distance(l),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.distance(new Line(new Point3D(0, -4, -4), new Point3D(0, -5, -5), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -5, -5), 1.0e-10)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.distance(new Line(new Point3D(0, -4, -4), new Point3D(0, -3, -4), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -3, -4), 1.0e-10)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.distance(new Line(new Point3D(0, -4, -4), new Point3D(1, -4, -4), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(0, -4, -4), Point3D.of(1, -4, -4), 1.0e-10)),
                             1.0e-10);
         Assert.assertEquals(Math.sqrt(8),
-                            l.distance(new Line(new Point3D(0, -4, 0), new Point3D(1, -4, 0), 1.0e-10)),
+                            l.distance(new Line(Point3D.of(0, -4, 0), Point3D.of(1, -4, 0), 1.0e-10)),
                             1.0e-10);
     }
 
     @Test
     public void testClosest() {
-        Line l = new Line(new Point3D(0, 1, 1), new Point3D(0, 2, 2), 1.0e-10);
+        Line l = new Line(Point3D.of(0, 1, 1), Point3D.of(0, 2, 2), 1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(new Line(new Point3D(1, 0, 1), new Point3D(1, 0, 2), 1.0e-10)).distance(new Point3D(0, 0, 0)),
+                            l.closestPoint(new Line(Point3D.of(1, 0, 1), Point3D.of(1, 0, 2), 1.0e-10)).distance(Point3D.of(0, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.5,
-                            l.closestPoint(new Line(new Point3D(-0.5, 0, 0), new Point3D(-0.5, -1, -1), 1.0e-10)).distance(new Point3D(-0.5, 0, 0)),
+                            l.closestPoint(new Line(Point3D.of(-0.5, 0, 0), Point3D.of(-0.5, -1, -1), 1.0e-10)).distance(Point3D.of(-0.5, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(l).distance(new Point3D(0, 0, 0)),
+                            l.closestPoint(l).distance(Point3D.of(0, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(new Line(new Point3D(0, -4, -4), new Point3D(0, -5, -5), 1.0e-10)).distance(new Point3D(0, 0, 0)),
+                            l.closestPoint(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -5, -5), 1.0e-10)).distance(Point3D.of(0, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(new Line(new Point3D(0, -4, -4), new Point3D(0, -3, -4), 1.0e-10)).distance(new Point3D(0, -4, -4)),
+                            l.closestPoint(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -3, -4), 1.0e-10)).distance(Point3D.of(0, -4, -4)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(new Line(new Point3D(0, -4, -4), new Point3D(1, -4, -4), 1.0e-10)).distance(new Point3D(0, -4, -4)),
+                            l.closestPoint(new Line(Point3D.of(0, -4, -4), Point3D.of(1, -4, -4), 1.0e-10)).distance(Point3D.of(0, -4, -4)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.closestPoint(new Line(new Point3D(0, -4, 0), new Point3D(1, -4, 0), 1.0e-10)).distance(new Point3D(0, -2, -2)),
+                            l.closestPoint(new Line(Point3D.of(0, -4, 0), Point3D.of(1, -4, 0), 1.0e-10)).distance(Point3D.of(0, -2, -2)),
                             1.0e-10);
     }
 
     @Test
     public void testIntersection() {
-        Line l = new Line(new Point3D(0, 1, 1), new Point3D(0, 2, 2), 1.0e-10);
-        Assert.assertNull(l.intersection(new Line(new Point3D(1, 0, 1), new Point3D(1, 0, 2), 1.0e-10)));
-        Assert.assertNull(l.intersection(new Line(new Point3D(-0.5, 0, 0), new Point3D(-0.5, -1, -1), 1.0e-10)));
+        Line l = new Line(Point3D.of(0, 1, 1), Point3D.of(0, 2, 2), 1.0e-10);
+        Assert.assertNull(l.intersection(new Line(Point3D.of(1, 0, 1), Point3D.of(1, 0, 2), 1.0e-10)));
+        Assert.assertNull(l.intersection(new Line(Point3D.of(-0.5, 0, 0), Point3D.of(-0.5, -1, -1), 1.0e-10)));
         Assert.assertEquals(0.0,
-                            l.intersection(l).distance(new Point3D(0, 0, 0)),
+                            l.intersection(l).distance(Point3D.of(0, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.intersection(new Line(new Point3D(0, -4, -4), new Point3D(0, -5, -5), 1.0e-10)).distance(new Point3D(0, 0, 0)),
+                            l.intersection(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -5, -5), 1.0e-10)).distance(Point3D.of(0, 0, 0)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.intersection(new Line(new Point3D(0, -4, -4), new Point3D(0, -3, -4), 1.0e-10)).distance(new Point3D(0, -4, -4)),
+                            l.intersection(new Line(Point3D.of(0, -4, -4), Point3D.of(0, -3, -4), 1.0e-10)).distance(Point3D.of(0, -4, -4)),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            l.intersection(new Line(new Point3D(0, -4, -4), new Point3D(1, -4, -4), 1.0e-10)).distance(new Point3D(0, -4, -4)),
+                            l.intersection(new Line(Point3D.of(0, -4, -4), Point3D.of(1, -4, -4), 1.0e-10)).distance(Point3D.of(0, -4, -4)),
                             1.0e-10);
-        Assert.assertNull(l.intersection(new Line(new Point3D(0, -4, 0), new Point3D(1, -4, 0), 1.0e-10)));
+        Assert.assertNull(l.intersection(new Line(Point3D.of(0, -4, 0), Point3D.of(1, -4, 0), 1.0e-10)));
     }
 
     @Test
     public void testRevert() {
 
         // setup
-        Line line = new Line(new Point3D(1653345.6696423641, 6170370.041579291, 90000),
-                             new Point3D(1650757.5050732433, 6160710.879908984, 0.9),
+        Line line = new Line(Point3D.of(1653345.6696423641, 6170370.041579291, 90000),
+                             Point3D.of(1650757.5050732433, 6160710.879908984, 0.9),
                              1.0e-10);
         Vector3D expected = line.getDirection().negate();
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PLYParser.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PLYParser.java
index 78ebefd..8a26afa 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PLYParser.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PLYParser.java
@@ -175,7 +175,7 @@ public PLYParser(final InputStream stream)
                     fields.get(zIndex).getToken() != Token.UNKNOWN) {
                     complain();
                 }
-                vertices[i] = new Point3D(Double.parseDouble(fields.get(xIndex).getValue()),
+                vertices[i] = Point3D.of(Double.parseDouble(fields.get(xIndex).getValue()),
                                            Double.parseDouble(fields.get(yIndex).getValue()),
                                            Double.parseDouble(fields.get(zIndex).getValue()));
             }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
index f6080a8..d6e3039 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
@@ -27,18 +27,18 @@
 
     @Test
     public void testContains() {
-        Plane p = new Plane(new Point3D(0, 0, 1), new Vector3D(0, 0, 1), 1.0e-10);
-        Assert.assertTrue(p.contains(new Point3D(0, 0, 1)));
-        Assert.assertTrue(p.contains(new Point3D(17, -32, 1)));
-        Assert.assertTrue(! p.contains(new Point3D(17, -32, 1.001)));
+        Plane p = new Plane(Point3D.of(0, 0, 1), Vector3D.of(0, 0, 1), 1.0e-10);
+        Assert.assertTrue(p.contains(Point3D.of(0, 0, 1)));
+        Assert.assertTrue(p.contains(Point3D.of(17, -32, 1)));
+        Assert.assertTrue(! p.contains(Point3D.of(17, -32, 1.001)));
     }
 
     @Test
     public void testOffset() {
-        Point3D p1 = new Point3D(1, 1, 1);
-        Plane p = new Plane(p1, new Vector3D(0.2, 0, 0), 1.0e-10);
-        Assert.assertEquals(-5.0, p.getOffset(new Point3D(-4, 0, 0)), 1.0e-10);
-        Assert.assertEquals(+5.0, p.getOffset(new Point3D(6, 10, -12)), 1.0e-10);
+        Point3D p1 = Point3D.of(1, 1, 1);
+        Plane p = new Plane(p1, Vector3D.of(0.2, 0, 0), 1.0e-10);
+        Assert.assertEquals(-5.0, p.getOffset(Point3D.of(-4, 0, 0)), 1.0e-10);
+        Assert.assertEquals(+5.0, p.getOffset(Point3D.of(6, 10, -12)), 1.0e-10);
         Assert.assertEquals(0.3,
                             p.getOffset(Point3D.vectorCombination(1.0, p1, 0.3, p.getNormal())),
                             1.0e-10);
@@ -49,15 +49,15 @@ public void testOffset() {
 
     @Test
     public void testPoint() {
-        Plane p = new Plane(new Point3D(2, -3, 1), new Vector3D(1, 4, 9), 1.0e-10);
+        Plane p = new Plane(Point3D.of(2, -3, 1), Vector3D.of(1, 4, 9), 1.0e-10);
         Assert.assertTrue(p.contains(p.getOrigin()));
     }
 
     @Test
     public void testThreePoints() {
-        Point3D p1 = new Point3D(1.2, 3.4, -5.8);
-        Point3D p2 = new Point3D(3.4, -5.8, 1.2);
-        Point3D p3 = new Point3D(-2.0, 4.3, 0.7);
+        Point3D p1 = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2 = Point3D.of(3.4, -5.8, 1.2);
+        Point3D p3 = Point3D.of(-2.0, 4.3, 0.7);
         Plane    p  = new Plane(p1, p2, p3, 1.0e-10);
         Assert.assertTrue(p.contains(p1));
         Assert.assertTrue(p.contains(p2));
@@ -66,9 +66,9 @@ public void testThreePoints() {
 
     @Test
     public void testRotate() {
-        Point3D p1 = new Point3D(1.2, 3.4, -5.8);
-        Point3D p2 = new Point3D(3.4, -5.8, 1.2);
-        Point3D p3 = new Point3D(-2.0, 4.3, 0.7);
+        Point3D p1 = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2 = Point3D.of(3.4, -5.8, 1.2);
+        Point3D p3 = Point3D.of(-2.0, 4.3, 0.7);
         Plane    p  = new Plane(p1, p2, p3, 1.0e-10);
         Vector3D oldNormal = p.getNormal();
 
@@ -91,9 +91,9 @@ public void testRotate() {
 
     @Test
     public void testTranslate() {
-        Point3D p1 = new Point3D(1.2, 3.4, -5.8);
-        Point3D p2 = new Point3D(3.4, -5.8, 1.2);
-        Point3D p3 = new Point3D(-2.0, 4.3, 0.7);
+        Point3D p1 = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2 = Point3D.of(3.4, -5.8, 1.2);
+        Point3D p3 = Point3D.of(-2.0, 4.3, 0.7);
         Plane    p  = new Plane(p1, p2, p3, 1.0e-10);
 
         p = p.translate(Vector3D.linearCombination(2.0, p.getU(), -1.5, p.getV()));
@@ -115,22 +115,22 @@ public void testTranslate() {
 
     @Test
     public void testIntersection() {
-        Plane p = new Plane(new Point3D(1, 2, 3), new Vector3D(-4, 1, -5), 1.0e-10);
-        Line  l = new Line(new Point3D(0.2, -3.5, 0.7), new Point3D(1.2, -2.5, -0.3), 1.0e-10);
+        Plane p = new Plane(Point3D.of(1, 2, 3), Vector3D.of(-4, 1, -5), 1.0e-10);
+        Line  l = new Line(Point3D.of(0.2, -3.5, 0.7), Point3D.of(1.2, -2.5, -0.3), 1.0e-10);
         Point3D point = p.intersection(l);
         Assert.assertTrue(p.contains(point));
         Assert.assertTrue(l.contains(point));
-        Assert.assertNull(p.intersection(new Line(new Point3D(10, 10, 10),
-                                                  new Point3D(10, 10, 10).add(p.getNormal().orthogonal()),
+        Assert.assertNull(p.intersection(new Line(Point3D.of(10, 10, 10),
+                                                  Point3D.of(10, 10, 10).add(p.getNormal().orthogonal()),
                                                   1.0e-10)));
     }
 
     @Test
     public void testIntersection2() {
-        Point3D p1  = new Point3D (1.2, 3.4, -5.8);
-        Point3D p2  = new Point3D (3.4, -5.8, 1.2);
-        Plane    pA  = new Plane(p1, p2, new Point3D (-2.0, 4.3, 0.7), 1.0e-10);
-        Plane    pB  = new Plane(p1, new Point3D (11.4, -3.8, 5.1), p2, 1.0e-10);
+        Point3D p1  = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2  = Point3D.of(3.4, -5.8, 1.2);
+        Plane    pA  = new Plane(p1, p2, Point3D.of(-2.0, 4.3, 0.7), 1.0e-10);
+        Plane    pB  = new Plane(p1, Point3D.of(11.4, -3.8, 5.1), p2, 1.0e-10);
         Line     l   = pA.intersection(pB);
         Assert.assertTrue(l.contains(p1));
         Assert.assertTrue(l.contains(p2));
@@ -139,10 +139,10 @@ public void testIntersection2() {
 
     @Test
     public void testIntersection3() {
-        Point3D reference = new Point3D (1.2, 3.4, -5.8);
-        Plane p1 = new Plane(reference, new Vector3D(1, 3, 3), 1.0e-10);
-        Plane p2 = new Plane(reference, new Vector3D(-2, 4, 0), 1.0e-10);
-        Plane p3 = new Plane(reference, new Vector3D(7, 0, -4), 1.0e-10);
+        Point3D reference = Point3D.of(1.2, 3.4, -5.8);
+        Plane p1 = new Plane(reference, Vector3D.of(1, 3, 3), 1.0e-10);
+        Plane p2 = new Plane(reference, Vector3D.of(-2, 4, 0), 1.0e-10);
+        Plane p3 = new Plane(reference, Vector3D.of(7, 0, -4), 1.0e-10);
         Point3D p = Plane.intersection(p1, p2, p3);
         Assert.assertEquals(reference.getX(), p.getX(), 1.0e-10);
         Assert.assertEquals(reference.getY(), p.getY(), 1.0e-10);
@@ -151,11 +151,11 @@ public void testIntersection3() {
 
     @Test
     public void testSimilar() {
-        Point3D p1  = new Point3D (1.2, 3.4, -5.8);
-        Point3D p2  = new Point3D (3.4, -5.8, 1.2);
-        Point3D p3  = new Point3D (-2.0, 4.3, 0.7);
+        Point3D p1  = Point3D.of(1.2, 3.4, -5.8);
+        Point3D p2  = Point3D.of(3.4, -5.8, 1.2);
+        Point3D p3  = Point3D.of(-2.0, 4.3, 0.7);
         Plane    pA  = new Plane(p1, p2, p3, 1.0e-10);
-        Plane    pB  = new Plane(p1, new Point3D (11.4, -3.8, 5.1), p2, 1.0e-10);
+        Plane    pB  = new Plane(p1, Point3D.of(11.4, -3.8, 5.1), p2, 1.0e-10);
         Assert.assertTrue(! pA.isSimilarTo(pB));
         Assert.assertTrue(pA.isSimilarTo(pA));
         Assert.assertTrue(pA.isSimilarTo(new Plane(p1, p3, p2, 1.0e-10)));
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
index fa4cf3f..62218a6 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
@@ -173,7 +173,7 @@ public void testEquals() {
     public void testToString() {
         // arrange
         Point3D p = Point3D.of(1, 2, 3);
-        Pattern pattern = Pattern.compile("\\(1.{0,2}; 2.{0,2}; 3.{0,2}\\)");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)");
 
         // act
         String str = p.toString();
@@ -183,6 +183,25 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point3D.parse("(1, 2, 0)"), 1, 2, 0);
+        checkPoint(Point3D.parse("(-1, -2, 0)"), -1, -2, 0);
+
+        checkPoint(Point3D.parse("(0.01, -1e-3, 1e3)"), 1e-2, -1e-3, 1e3);
+
+        checkPoint(Point3D.parse("(NaN, -Infinity, Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+        checkPoint(Point3D.parse(Point3D.ZERO.toString()), 0, 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point3D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
index 2e04ec1..fe6c74d 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
@@ -60,11 +60,11 @@ public void testWholeSpace() {
         Assert.assertTrue(polySet.isFull());
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
-                new Point3D(-100, -100, -100),
-                new Point3D(0, 0, 0),
-                new Point3D(100, 100, 100),
-                new Point3D(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
+                Point3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
+                Point3D.of(-100, -100, -100),
+                Point3D.of(0, 0, 0),
+                Point3D.of(100, 100, 100),
+                Point3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
     }
 
     @Test
@@ -81,11 +81,11 @@ public void testEmptyRegion() {
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
-                new Point3D(-100, -100, -100),
-                new Point3D(0, 0, 0),
-                new Point3D(100, 100, 100),
-                new Point3D(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
+                Point3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
+                Point3D.of(-100, -100, -100),
+                Point3D.of(0, 0, 0),
+                Point3D.of(100, 100, 100),
+                Point3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
     }
 
     @Test
@@ -107,12 +107,12 @@ public void testHalfSpace() {
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
-                new Point3D(-100, -100, -100));
-        checkPoints(Region.Location.BOUNDARY, polySet, new Point3D(0, 0, 0));
+                Point3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
+                Point3D.of(-100, -100, -100));
+        checkPoints(Region.Location.BOUNDARY, polySet, Point3D.of(0, 0, 0));
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(100, 100, 100),
-                new Point3D(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
+                Point3D.of(100, 100, 100),
+                Point3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
     }
 
     @Test
@@ -133,12 +133,12 @@ public void testInvertedRegion() {
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
-                new Point3D(-100, -100, -100),
-                new Point3D(100, 100, 100),
-                new Point3D(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
+                Point3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
+                Point3D.of(-100, -100, -100),
+                Point3D.of(100, 100, 100),
+                Point3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(0, 0, 0));
+                Point3D.of(0, 0, 0));
     }
 
     @Test
@@ -175,50 +175,50 @@ public void testCreateFromBoundaries_unitBox() {
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(1, 0, 0),
-                new Point3D(0, -1, 0),
-                new Point3D(0, 1, 0),
-                new Point3D(0, 0, -1),
-                new Point3D(0, 0, 1),
-
-                new Point3D(1, 1, 1),
-                new Point3D(1, 1, -1),
-                new Point3D(1, -1, 1),
-                new Point3D(1, -1, -1),
-                new Point3D(-1, 1, 1),
-                new Point3D(-1, 1, -1),
-                new Point3D(-1, -1, 1),
-                new Point3D(-1, -1, -1));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(1, 0, 0),
+                Point3D.of(0, -1, 0),
+                Point3D.of(0, 1, 0),
+                Point3D.of(0, 0, -1),
+                Point3D.of(0, 0, 1),
+
+                Point3D.of(1, 1, 1),
+                Point3D.of(1, 1, -1),
+                Point3D.of(1, -1, 1),
+                Point3D.of(1, -1, -1),
+                Point3D.of(-1, 1, 1),
+                Point3D.of(-1, 1, -1),
+                Point3D.of(-1, -1, 1),
+                Point3D.of(-1, -1, -1));
 
         checkPoints(Region.Location.BOUNDARY, polySet,
-                new Point3D(0.5, 0, 0),
-                new Point3D(-0.5, 0, 0),
-                new Point3D(0, 0.5, 0),
-                new Point3D(0, -0.5, 0),
-                new Point3D(0, 0, 0.5),
-                new Point3D(0, 0, -0.5),
-
-                new Point3D(0.5, 0.5, 0.5),
-                new Point3D(0.5, 0.5, -0.5),
-                new Point3D(0.5, -0.5, 0.5),
-                new Point3D(0.5, -0.5, -0.5),
-                new Point3D(-0.5, 0.5, 0.5),
-                new Point3D(-0.5, 0.5, -0.5),
-                new Point3D(-0.5, -0.5, 0.5),
-                new Point3D(-0.5, -0.5, -0.5));
+                Point3D.of(0.5, 0, 0),
+                Point3D.of(-0.5, 0, 0),
+                Point3D.of(0, 0.5, 0),
+                Point3D.of(0, -0.5, 0),
+                Point3D.of(0, 0, 0.5),
+                Point3D.of(0, 0, -0.5),
+
+                Point3D.of(0.5, 0.5, 0.5),
+                Point3D.of(0.5, 0.5, -0.5),
+                Point3D.of(0.5, -0.5, 0.5),
+                Point3D.of(0.5, -0.5, -0.5),
+                Point3D.of(-0.5, 0.5, 0.5),
+                Point3D.of(-0.5, 0.5, -0.5),
+                Point3D.of(-0.5, -0.5, 0.5),
+                Point3D.of(-0.5, -0.5, -0.5));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-
-                new Point3D(0.4, 0.4, 0.4),
-                new Point3D(0.4, 0.4, -0.4),
-                new Point3D(0.4, -0.4, 0.4),
-                new Point3D(0.4, -0.4, -0.4),
-                new Point3D(-0.4, 0.4, 0.4),
-                new Point3D(-0.4, 0.4, -0.4),
-                new Point3D(-0.4, -0.4, 0.4),
-                new Point3D(-0.4, -0.4, -0.4));
+                Point3D.of(0, 0, 0),
+
+                Point3D.of(0.4, 0.4, 0.4),
+                Point3D.of(0.4, 0.4, -0.4),
+                Point3D.of(0.4, -0.4, 0.4),
+                Point3D.of(0.4, -0.4, -0.4),
+                Point3D.of(-0.4, 0.4, 0.4),
+                Point3D.of(-0.4, 0.4, -0.4),
+                Point3D.of(-0.4, -0.4, 0.4),
+                Point3D.of(-0.4, -0.4, -0.4));
     }
 
     @Test
@@ -226,7 +226,7 @@ public void testCreateFromBoundaries_twoBoxes_disjoint() {
         // arrange
         List<SubHyperplane<Point3D>> boundaries = new ArrayList<>();
         boundaries.addAll(createBoxBoundaries(Point3D.ZERO, 1.0, TEST_TOLERANCE));
-        boundaries.addAll(createBoxBoundaries(new Point3D(2, 0, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(2, 0, 0), 1.0, TEST_TOLERANCE));
 
         // act
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_TOLERANCE);
@@ -235,26 +235,26 @@ public void testCreateFromBoundaries_twoBoxes_disjoint() {
         Assert.assertEquals(TEST_TOLERANCE, polySet.getTolerance(), Precision.EPSILON);
         Assert.assertEquals(2.0, polySet.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(1, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(1, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(1, 0, 0),
-                new Point3D(3, 0, 0));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(1, 0, 0),
+                Point3D.of(3, 0, 0));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-                new Point3D(2, 0, 0));
+                Point3D.of(0, 0, 0),
+                Point3D.of(2, 0, 0));
     }
 
     @Test
     public void testCreateFromBoundaries_twoBoxes_sharedSide() {
         // arrange
         List<SubHyperplane<Point3D>> boundaries = new ArrayList<>();
-        boundaries.addAll(createBoxBoundaries(new Point3D(0, 0, 0), 1.0, TEST_TOLERANCE));
-        boundaries.addAll(createBoxBoundaries(new Point3D(1, 0, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(0, 0, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(1, 0, 0), 1.0, TEST_TOLERANCE));
 
         // act
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_TOLERANCE);
@@ -263,17 +263,17 @@ public void testCreateFromBoundaries_twoBoxes_sharedSide() {
         Assert.assertEquals(TEST_TOLERANCE, polySet.getTolerance(), Precision.EPSILON);
         Assert.assertEquals(2.0, polySet.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(10.0, polySet.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(0.5, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(0.5, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(2, 0, 0));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(2, 0, 0));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-                new Point3D(1, 0, 0));
+                Point3D.of(0, 0, 0),
+                Point3D.of(1, 0, 0));
     }
 
     @Test
@@ -281,8 +281,8 @@ public void testCreateFromBoundaries_twoBoxes_separationLessThanTolerance() {
         // arrange
         double tolerance = 1e-6;
         List<SubHyperplane<Point3D>> boundaries = new ArrayList<>();
-        boundaries.addAll(createBoxBoundaries(new Point3D(0, 0, 0), 1.0, tolerance));
-        boundaries.addAll(createBoxBoundaries(new Point3D(1 + 1e-7, 0, 0), 1.0, tolerance));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(0, 0, 0), 1.0, tolerance));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(1 + 1e-7, 0, 0), 1.0, tolerance));
 
         // act
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, tolerance);
@@ -291,25 +291,25 @@ public void testCreateFromBoundaries_twoBoxes_separationLessThanTolerance() {
         Assert.assertEquals(tolerance, polySet.getTolerance(), Precision.EPSILON);
         Assert.assertEquals(2.0, polySet.getSize(), tolerance);
         Assert.assertEquals(10.0, polySet.getBoundarySize(), tolerance);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(0.5 + 5e-8, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(0.5 + 5e-8, 0, 0), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(2, 0, 0));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(2, 0, 0));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-                new Point3D(1, 0, 0));
+                Point3D.of(0, 0, 0),
+                Point3D.of(1, 0, 0));
     }
 
     @Test
     public void testCreateFromBoundaries_twoBoxes_sharedEdge() {
         // arrange
         List<SubHyperplane<Point3D>> boundaries = new ArrayList<>();
-        boundaries.addAll(createBoxBoundaries(new Point3D(0, 0, 0), 1.0, TEST_TOLERANCE));
-        boundaries.addAll(createBoxBoundaries(new Point3D(1, 1, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(0, 0, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(1, 1, 0), 1.0, TEST_TOLERANCE));
 
         // act
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_TOLERANCE);
@@ -318,27 +318,27 @@ public void testCreateFromBoundaries_twoBoxes_sharedEdge() {
         Assert.assertEquals(TEST_TOLERANCE, polySet.getTolerance(), Precision.EPSILON);
         Assert.assertEquals(2.0, polySet.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(0.5, 0.5, 0), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(0.5, 0.5, 0), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(1, 0, 0),
-                new Point3D(0, 1, 0),
-                new Point3D(2, 1, 0));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(1, 0, 0),
+                Point3D.of(0, 1, 0),
+                Point3D.of(2, 1, 0));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-                new Point3D(1, 1, 0));
+                Point3D.of(0, 0, 0),
+                Point3D.of(1, 1, 0));
     }
 
     @Test
     public void testCreateFromBoundaries_twoBoxes_sharedPoint() {
         // arrange
         List<SubHyperplane<Point3D>> boundaries = new ArrayList<>();
-        boundaries.addAll(createBoxBoundaries(new Point3D(0, 0, 0), 1.0, TEST_TOLERANCE));
-        boundaries.addAll(createBoxBoundaries(new Point3D(1, 1, 1), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(0, 0, 0), 1.0, TEST_TOLERANCE));
+        boundaries.addAll(createBoxBoundaries(Point3D.of(1, 1, 1), 1.0, TEST_TOLERANCE));
 
         // act
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_TOLERANCE);
@@ -347,19 +347,19 @@ public void testCreateFromBoundaries_twoBoxes_sharedPoint() {
         Assert.assertEquals(TEST_TOLERANCE, polySet.getTolerance(), Precision.EPSILON);
         Assert.assertEquals(2.0, polySet.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(0.5, 0.5, 0.5), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(0.5, 0.5, 0.5), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-1, 0, 0),
-                new Point3D(1, 0, 0),
-                new Point3D(0, 1, 1),
-                new Point3D(2, 1, 1));
+                Point3D.of(-1, 0, 0),
+                Point3D.of(1, 0, 0),
+                Point3D.of(0, 1, 1),
+                Point3D.of(2, 1, 1));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(0, 0, 0),
-                new Point3D(1, 1, 1));
+                Point3D.of(0, 0, 0),
+                Point3D.of(1, 1, 1));
     }
 
     @Test
@@ -370,7 +370,7 @@ public void testCreateBox() {
         // assert
         Assert.assertEquals(1.0, tree.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(6.0, tree.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(0.5, 0.5, 0.5), tree.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(0.5, 0.5, 0.5), tree.getBarycenter(), TEST_TOLERANCE);
 
         for (double x = -0.25; x < 1.25; x += 0.1) {
             boolean xOK = (x >= 0.0) && (x <= 1.0);
@@ -380,25 +380,25 @@ public void testCreateBox() {
                     boolean zOK = (z >= 0.0) && (z <= 1.0);
                     Region.Location expected =
                         (xOK && yOK && zOK) ? Region.Location.INSIDE : Region.Location.OUTSIDE;
-                    Assert.assertEquals(expected, tree.checkPoint(new Point3D(x, y, z)));
+                    Assert.assertEquals(expected, tree.checkPoint(Point3D.of(x, y, z)));
                 }
             }
         }
         checkPoints(Region.Location.BOUNDARY, tree, new Point3D[] {
-            new Point3D(0.0, 0.5, 0.5),
-            new Point3D(1.0, 0.5, 0.5),
-            new Point3D(0.5, 0.0, 0.5),
-            new Point3D(0.5, 1.0, 0.5),
-            new Point3D(0.5, 0.5, 0.0),
-            new Point3D(0.5, 0.5, 1.0)
+            Point3D.of(0.0, 0.5, 0.5),
+            Point3D.of(1.0, 0.5, 0.5),
+            Point3D.of(0.5, 0.0, 0.5),
+            Point3D.of(0.5, 1.0, 0.5),
+            Point3D.of(0.5, 0.5, 0.0),
+            Point3D.of(0.5, 0.5, 1.0)
         });
         checkPoints(Region.Location.OUTSIDE, tree, new Point3D[] {
-            new Point3D(0.0, 1.2, 1.2),
-            new Point3D(1.0, 1.2, 1.2),
-            new Point3D(1.2, 0.0, 1.2),
-            new Point3D(1.2, 1.0, 1.2),
-            new Point3D(1.2, 1.2, 0.0),
-            new Point3D(1.2, 1.2, 1.0)
+            Point3D.of(0.0, 1.2, 1.2),
+            Point3D.of(1.0, 1.2, 1.2),
+            Point3D.of(1.2, 0.0, 1.2),
+            Point3D.of(1.2, 1.0, 1.2),
+            Point3D.of(1.2, 1.2, 0.0),
+            Point3D.of(1.2, 1.2, 1.0)
         });
     }
 
@@ -427,35 +427,35 @@ public void testInvertedBox() {
                     boolean zOK = (z < 0.0) || (z > 1.0);
                     Region.Location expected =
                         (xOK || yOK || zOK) ? Region.Location.INSIDE : Region.Location.OUTSIDE;
-                    Assert.assertEquals(expected, tree.checkPoint(new Point3D(x, y, z)));
+                    Assert.assertEquals(expected, tree.checkPoint(Point3D.of(x, y, z)));
                 }
             }
         }
         checkPoints(Region.Location.BOUNDARY, tree, new Point3D[] {
-            new Point3D(0.0, 0.5, 0.5),
-            new Point3D(1.0, 0.5, 0.5),
-            new Point3D(0.5, 0.0, 0.5),
-            new Point3D(0.5, 1.0, 0.5),
-            new Point3D(0.5, 0.5, 0.0),
-            new Point3D(0.5, 0.5, 1.0)
+            Point3D.of(0.0, 0.5, 0.5),
+            Point3D.of(1.0, 0.5, 0.5),
+            Point3D.of(0.5, 0.0, 0.5),
+            Point3D.of(0.5, 1.0, 0.5),
+            Point3D.of(0.5, 0.5, 0.0),
+            Point3D.of(0.5, 0.5, 1.0)
         });
         checkPoints(Region.Location.INSIDE, tree, new Point3D[] {
-            new Point3D(0.0, 1.2, 1.2),
-            new Point3D(1.0, 1.2, 1.2),
-            new Point3D(1.2, 0.0, 1.2),
-            new Point3D(1.2, 1.0, 1.2),
-            new Point3D(1.2, 1.2, 0.0),
-            new Point3D(1.2, 1.2, 1.0)
+            Point3D.of(0.0, 1.2, 1.2),
+            Point3D.of(1.0, 1.2, 1.2),
+            Point3D.of(1.2, 0.0, 1.2),
+            Point3D.of(1.2, 1.0, 1.2),
+            Point3D.of(1.2, 1.2, 0.0),
+            Point3D.of(1.2, 1.2, 1.0)
         });
     }
 
     @Test
     public void testTetrahedron() {
         // arrange
-        Point3D vertex1 = new Point3D(1, 2, 3);
-        Point3D vertex2 = new Point3D(2, 2, 4);
-        Point3D vertex3 = new Point3D(2, 3, 3);
-        Point3D vertex4 = new Point3D(1, 3, 4);
+        Point3D vertex1 = Point3D.of(1, 2, 3);
+        Point3D vertex2 = Point3D.of(2, 2, 4);
+        Point3D vertex3 = Point3D.of(2, 3, 3);
+        Point3D vertex4 = Point3D.of(1, 3, 4);
 
         // act
         PolyhedronsSet tree =
@@ -468,7 +468,7 @@ public void testTetrahedron() {
         // assert
         Assert.assertEquals(1.0 / 3.0, tree.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(2.0 * Math.sqrt(3.0), tree.getBoundarySize(), TEST_TOLERANCE);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(1.5, 2.5, 3.5), tree.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(1.5, 2.5, 3.5), tree.getBarycenter(), TEST_TOLERANCE);
 
         double third = 1.0 / 3.0;
         checkPoints(Region.Location.BOUNDARY, tree, new Point3D[] {
@@ -479,10 +479,10 @@ public void testTetrahedron() {
             Point3D.vectorCombination(third, vertex4, third, vertex1, third, vertex2)
         });
         checkPoints(Region.Location.OUTSIDE, tree, new Point3D[] {
-            new Point3D(1, 2, 4),
-            new Point3D(2, 2, 3),
-            new Point3D(2, 3, 4),
-            new Point3D(1, 3, 3)
+            Point3D.of(1, 2, 4),
+            Point3D.of(2, 2, 3),
+            Point3D.of(2, 3, 4),
+            Point3D.of(1, 3, 3)
         });
     }
 
@@ -494,42 +494,42 @@ public void testSphere() {
         double radius = 1.0;
 
         // act
-        PolyhedronsSet polySet = createSphere(new Point3D(1, 2, 3), radius, 8, 16);
+        PolyhedronsSet polySet = createSphere(Point3D.of(1, 2, 3), radius, 8, 16);
 
         // assert
         Assert.assertEquals(sphereVolume(radius), polySet.getSize(), approximationTolerance);
         Assert.assertEquals(sphereSurface(radius), polySet.getBoundarySize(), approximationTolerance);
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(1, 2, 3), polySet.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(1, 2, 3), polySet.getBarycenter(), TEST_TOLERANCE);
         Assert.assertFalse(polySet.isEmpty());
         Assert.assertFalse(polySet.isFull());
 
         checkPoints(Region.Location.OUTSIDE, polySet,
-                new Point3D(-0.1, 2, 3),
-                new Point3D(2.1, 2, 3),
-                new Point3D(1, 0.9, 3),
-                new Point3D(1, 3.1, 3),
-                new Point3D(1, 2, 1.9),
-                new Point3D(1, 2, 4.1),
-                new Point3D(1.6, 2.6, 3.6));
+                Point3D.of(-0.1, 2, 3),
+                Point3D.of(2.1, 2, 3),
+                Point3D.of(1, 0.9, 3),
+                Point3D.of(1, 3.1, 3),
+                Point3D.of(1, 2, 1.9),
+                Point3D.of(1, 2, 4.1),
+                Point3D.of(1.6, 2.6, 3.6));
 
         checkPoints(Region.Location.INSIDE, polySet,
-                new Point3D(1, 2, 3),
-                new Point3D(0.1, 2, 3),
-                new Point3D(1.9, 2, 3),
-                new Point3D(1, 2.1, 3),
-                new Point3D(1, 2.9, 3),
-                new Point3D(1, 2, 2.1),
-                new Point3D(1, 2, 3.9),
-                new Point3D(1.5, 2.5, 3.5));
+                Point3D.of(1, 2, 3),
+                Point3D.of(0.1, 2, 3),
+                Point3D.of(1.9, 2, 3),
+                Point3D.of(1, 2.1, 3),
+                Point3D.of(1, 2.9, 3),
+                Point3D.of(1, 2, 2.1),
+                Point3D.of(1, 2, 3.9),
+                Point3D.of(1.5, 2.5, 3.5));
     }
 
     @Test
     public void testIsometry() {
         // arrange
-        Point3D vertex1 = new Point3D(1.1, 2.2, 3.3);
-        Point3D vertex2 = new Point3D(2.0, 2.4, 4.2);
-        Point3D vertex3 = new Point3D(2.8, 3.3, 3.7);
-        Point3D vertex4 = new Point3D(1.0, 3.6, 4.5);
+        Point3D vertex1 = Point3D.of(1.1, 2.2, 3.3);
+        Point3D vertex2 = Point3D.of(2.0, 2.4, 4.2);
+        Point3D vertex3 = Point3D.of(2.8, 3.3, 3.7);
+        Point3D vertex4 = Point3D.of(1.0, 3.6, 4.5);
 
         // act
         PolyhedronsSet tree =
@@ -541,9 +541,9 @@ public void testIsometry() {
 
         // assert
         Point3D barycenter = tree.getBarycenter();
-        Vector3D s = new Vector3D(10.2, 4.3, -6.7);
-        Point3D c = new Point3D(-0.2, 2.1, -3.2);
-        Rotation r = new Rotation(new Vector3D(6.2, -4.4, 2.1), 0.12, RotationConvention.VECTOR_OPERATOR);
+        Vector3D s = Vector3D.of(10.2, 4.3, -6.7);
+        Point3D c = Point3D.of(-0.2, 2.1, -3.2);
+        Rotation r = new Rotation(Vector3D.of(6.2, -4.4, 2.1), 0.12, RotationConvention.VECTOR_OPERATOR);
 
         tree = tree.rotate(c, r).translate(s);
 
@@ -626,7 +626,7 @@ public void testBuildBox() {
             new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, TEST_TOLERANCE);
 
         // assert
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(x, y, z), tree.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(x, y, z), tree.getBarycenter(), TEST_TOLERANCE);
         Assert.assertEquals(8 * l * w * w, tree.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(8 * w * (2 * l + w), tree.getBoundarySize(), TEST_TOLERANCE);
     }
@@ -651,7 +651,7 @@ public void testCross() {
         PolyhedronsSet tree = (PolyhedronsSet) factory.union(xBeam, factory.union(yBeam, zBeam));
 
         // assert
-        EuclideanTestUtils.assertCoordinatesEqual(new Point3D(x, y, z), tree.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point3D.of(x, y, z), tree.getBarycenter(), TEST_TOLERANCE);
         Assert.assertEquals(8 * w * w * (3 * l - 2 * w), tree.getSize(), TEST_TOLERANCE);
         Assert.assertEquals(24 * w * (2 * l - w), tree.getBoundarySize(), TEST_TOLERANCE);
     }
@@ -682,9 +682,9 @@ public void testCreateFromBoundaries_handlesSmallBoundariesCreatedDuringConstruc
             int idxA = indices[idx] * 3;
             int idxB = indices[idx + 1] * 3;
             int idxC = indices[idx + 2] * 3;
-            Point3D v_1 = new Point3D(coords[idxA], coords[idxA + 1], coords[idxA + 2]);
-            Point3D v_2 = new Point3D(coords[idxB], coords[idxB + 1], coords[idxB + 2]);
-            Point3D v_3 = new Point3D(coords[idxC], coords[idxC + 1], coords[idxC + 2]);
+            Point3D v_1 = Point3D.of(coords[idxA], coords[idxA + 1], coords[idxA + 2]);
+            Point3D v_2 = Point3D.of(coords[idxB], coords[idxB + 1], coords[idxB + 2]);
+            Point3D v_3 = Point3D.of(coords[idxC], coords[idxC + 1], coords[idxC + 2]);
             Point3D[] vertices = {v_1, v_2, v_3};
             Plane polyPlane = new Plane(v_1, v_2, v_3, TEST_TOLERANCE);
             ArrayList<SubHyperplane<Point2D>> lines = new ArrayList<>();
@@ -745,14 +745,14 @@ public void testDumpParse() throws IOException, ParseException {
         double xmin=-1,xmax=1;
         double ymin=-1,ymax=1;
         double zmin=-1,zmax=1;
-        verts[0]=new Point3D(xmin,ymin,zmin);
-        verts[1]=new Point3D(xmax,ymin,zmin);
-        verts[2]=new Point3D(xmax,ymax,zmin);
-        verts[3]=new Point3D(xmin,ymax,zmin);
-        verts[4]=new Point3D(xmin,ymin,zmax);
-        verts[5]=new Point3D(xmax,ymin,zmax);
-        verts[6]=new Point3D(xmax,ymax,zmax);
-        verts[7]=new Point3D(xmin,ymax,zmax);
+        verts[0]=Point3D.of(xmin,ymin,zmin);
+        verts[1]=Point3D.of(xmax,ymin,zmin);
+        verts[2]=Point3D.of(xmax,ymax,zmin);
+        verts[3]=Point3D.of(xmin,ymax,zmin);
+        verts[4]=Point3D.of(xmin,ymin,zmax);
+        verts[5]=Point3D.of(xmax,ymin,zmax);
+        verts[6]=Point3D.of(xmax,ymax,zmax);
+        verts[7]=Point3D.of(xmin,ymax,zmax);
         //
         int[][] faces=new int[12][];
         faces[0]=new int[]{3,1,0};  // bottom (-z)
@@ -815,7 +815,7 @@ public void testCreateFromBRep_badOrientation() throws IOException, ParseExcepti
 
     @Test
     public void testCreateFromBRep_wrongNumberOfPoints() throws IOException, ParseException {
-        checkError(Arrays.asList(Point3D.ZERO, new Point3D(1, 0, 0), new Point3D(0, 1, 0), new Point3D(0, 0, 1)),
+        checkError(Arrays.asList(Point3D.ZERO, Point3D.of(1, 0, 0), Point3D.of(0, 1, 0), Point3D.of(0, 0, 1)),
                    Arrays.asList(new int[] { 0, 1, 2 }, new int[] {2, 3}),
                    "");
     }
@@ -849,45 +849,45 @@ public void testFirstIntersection() {
         List<SubHyperplane<Point3D>> boundaries = createBoxBoundaries(Point3D.ZERO, 2.0, TEST_TOLERANCE);
         PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_TOLERANCE);
 
-        Line xPlus = new Line(Point3D.ZERO, new Point3D(1, 0, 0), TEST_TOLERANCE);
-        Line xMinus = new Line(Point3D.ZERO, new Point3D(-1, 0, 0), TEST_TOLERANCE);
+        Line xPlus = new Line(Point3D.ZERO, Point3D.of(1, 0, 0), TEST_TOLERANCE);
+        Line xMinus = new Line(Point3D.ZERO, Point3D.of(-1, 0, 0), TEST_TOLERANCE);
 
-        Line yPlus = new Line(Point3D.ZERO, new Point3D(0, 1, 0), TEST_TOLERANCE);
-        Line yMinus = new Line(Point3D.ZERO, new Point3D(0, -1, 0), TEST_TOLERANCE);
+        Line yPlus = new Line(Point3D.ZERO, Point3D.of(0, 1, 0), TEST_TOLERANCE);
+        Line yMinus = new Line(Point3D.ZERO, Point3D.of(0, -1, 0), TEST_TOLERANCE);
 
-        Line zPlus = new Line(Point3D.ZERO, new Point3D(0, 0, 1), TEST_TOLERANCE);
-        Line zMinus = new Line(Point3D.ZERO, new Point3D(0, 0, -1), TEST_TOLERANCE);
+        Line zPlus = new Line(Point3D.ZERO, Point3D.of(0, 0, 1), TEST_TOLERANCE);
+        Line zMinus = new Line(Point3D.ZERO, Point3D.of(0, 0, -1), TEST_TOLERANCE);
 
         // act/assert
-        assertSubPlaneNormal(new Vector3D(-1, 0, 0), polySet.firstIntersection(new Point3D(-1.1, 0, 0), xPlus));
-        assertSubPlaneNormal(new Vector3D(-1, 0, 0), polySet.firstIntersection(new Point3D(-1, 0, 0), xPlus));
-        assertSubPlaneNormal(new Vector3D(1, 0, 0), polySet.firstIntersection(new Point3D(-0.9, 0, 0), xPlus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(1.1, 0, 0), xPlus));
-
-        assertSubPlaneNormal(new Vector3D(1, 0, 0), polySet.firstIntersection(new Point3D(1.1, 0, 0), xMinus));
-        assertSubPlaneNormal(new Vector3D(1, 0, 0), polySet.firstIntersection(new Point3D(1, 0, 0), xMinus));
-        assertSubPlaneNormal(new Vector3D(-1, 0, 0), polySet.firstIntersection(new Point3D(0.9, 0, 0), xMinus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(-1.1, 0, 0), xMinus));
-
-        assertSubPlaneNormal(new Vector3D(0, -1, 0), polySet.firstIntersection(new Point3D(0, -1.1, 0), yPlus));
-        assertSubPlaneNormal(new Vector3D(0, -1, 0), polySet.firstIntersection(new Point3D(0, -1, 0), yPlus));
-        assertSubPlaneNormal(new Vector3D(0, 1, 0), polySet.firstIntersection(new Point3D(0, -0.9, 0), yPlus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(0, 1.1, 0), yPlus));
-
-        assertSubPlaneNormal(new Vector3D(0, 1, 0), polySet.firstIntersection(new Point3D(0, 1.1, 0), yMinus));
-        assertSubPlaneNormal(new Vector3D(0, 1, 0), polySet.firstIntersection(new Point3D(0, 1, 0), yMinus));
-        assertSubPlaneNormal(new Vector3D(0, -1, 0), polySet.firstIntersection(new Point3D(0, 0.9, 0), yMinus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(0, -1.1, 0), yMinus));
-
-        assertSubPlaneNormal(new Vector3D(0, 0, -1), polySet.firstIntersection(new Point3D(0, 0, -1.1), zPlus));
-        assertSubPlaneNormal(new Vector3D(0, 0, -1), polySet.firstIntersection(new Point3D(0, 0, -1), zPlus));
-        assertSubPlaneNormal(new Vector3D(0, 0, 1), polySet.firstIntersection(new Point3D(0, 0, -0.9), zPlus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(0, 0, 1.1), zPlus));
-
-        assertSubPlaneNormal(new Vector3D(0, 0, 1), polySet.firstIntersection(new Point3D(0, 0, 1.1), zMinus));
-        assertSubPlaneNormal(new Vector3D(0, 0, 1), polySet.firstIntersection(new Point3D(0, 0, 1), zMinus));
-        assertSubPlaneNormal(new Vector3D(0, 0, -1), polySet.firstIntersection(new Point3D(0, 0, 0.9), zMinus));
-        Assert.assertEquals(null, polySet.firstIntersection(new Point3D(0, 0, -1.1), zMinus));
+        assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Point3D.of(-1.1, 0, 0), xPlus));
+        assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Point3D.of(-1, 0, 0), xPlus));
+        assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Point3D.of(-0.9, 0, 0), xPlus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(1.1, 0, 0), xPlus));
+
+        assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Point3D.of(1.1, 0, 0), xMinus));
+        assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Point3D.of(1, 0, 0), xMinus));
+        assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Point3D.of(0.9, 0, 0), xMinus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(-1.1, 0, 0), xMinus));
+
+        assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Point3D.of(0, -1.1, 0), yPlus));
+        assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Point3D.of(0, -1, 0), yPlus));
+        assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Point3D.of(0, -0.9, 0), yPlus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(0, 1.1, 0), yPlus));
+
+        assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Point3D.of(0, 1.1, 0), yMinus));
+        assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Point3D.of(0, 1, 0), yMinus));
+        assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Point3D.of(0, 0.9, 0), yMinus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(0, -1.1, 0), yMinus));
+
+        assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Point3D.of(0, 0, -1.1), zPlus));
+        assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Point3D.of(0, 0, -1), zPlus));
+        assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Point3D.of(0, 0, -0.9), zPlus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(0, 0, 1.1), zPlus));
+
+        assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Point3D.of(0, 0, 1.1), zMinus));
+        assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Point3D.of(0, 0, 1), zMinus));
+        assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Point3D.of(0, 0, 0.9), zMinus));
+        Assert.assertEquals(null, polySet.firstIntersection(Point3D.of(0, 0, -1.1), zMinus));
     }
 
     // Issue 1211
@@ -902,7 +902,7 @@ public void testFirstIntersection_onlyReturnsPointsInDirectionOfRay() throws IOE
         int nrays = 1000;
         for (int i = 0; i < nrays; i++) {
             Point3D origin    = Point3D.ZERO;
-            Vector3D direction = new Vector3D(2 * random.nextDouble() - 1,
+            Vector3D direction = Vector3D.of(2 * random.nextDouble() - 1,
                                               2 * random.nextDouble() - 1,
                                               2 * random.nextDouble() - 1).normalize();
             Line line = new Line(origin, origin.add(direction), polyset.getTolerance());
@@ -922,7 +922,7 @@ public void testBoolean_union() throws IOException {
         double size = 1.0;
         double radius = size * 0.5;
         PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_TOLERANCE);
-        PolyhedronsSet sphere = createSphere(new Point3D(size * 0.5, size * 0.5, size), radius, 8, 16);
+        PolyhedronsSet sphere = createSphere(Point3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
 
         // act
         PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Point3D>().union(box, sphere);
@@ -938,20 +938,20 @@ public void testBoolean_union() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, 0.5, 0.5),
-                new Point3D(1.1, 0.5, 0.5),
-                new Point3D(0.5, -0.1, 0.5),
-                new Point3D(0.5, 1.1, 0.5),
-                new Point3D(0.5, 0.5, -0.1),
-                new Point3D(0.5, 0.5, 1.6));
+                Point3D.of(-0.1, 0.5, 0.5),
+                Point3D.of(1.1, 0.5, 0.5),
+                Point3D.of(0.5, -0.1, 0.5),
+                Point3D.of(0.5, 1.1, 0.5),
+                Point3D.of(0.5, 0.5, -0.1),
+                Point3D.of(0.5, 0.5, 1.6));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.5, 0.5),
-                new Point3D(0.9, 0.5, 0.5),
-                new Point3D(0.5, 0.1, 0.5),
-                new Point3D(0.5, 0.9, 0.5),
-                new Point3D(0.5, 0.5, 0.1),
-                new Point3D(0.5, 0.5, 1.4));
+                Point3D.of(0.1, 0.5, 0.5),
+                Point3D.of(0.9, 0.5, 0.5),
+                Point3D.of(0.5, 0.1, 0.5),
+                Point3D.of(0.5, 0.9, 0.5),
+                Point3D.of(0.5, 0.5, 0.1),
+                Point3D.of(0.5, 0.5, 1.4));
     }
 
     @Test
@@ -973,20 +973,20 @@ public void testUnion_self() {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-1.1, 0, 0),
-                new Point3D(1.1, 0, 0),
-                new Point3D(0, -1.1, 0),
-                new Point3D(0, 1.1, 0),
-                new Point3D(0, 0, -1.1),
-                new Point3D(0, 0, 1.1));
+                Point3D.of(-1.1, 0, 0),
+                Point3D.of(1.1, 0, 0),
+                Point3D.of(0, -1.1, 0),
+                Point3D.of(0, 1.1, 0),
+                Point3D.of(0, 0, -1.1),
+                Point3D.of(0, 0, 1.1));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(-0.9, 0, 0),
-                new Point3D(0.9, 0, 0),
-                new Point3D(0, -0.9, 0),
-                new Point3D(0, 0.9, 0),
-                new Point3D(0, 0, -0.9),
-                new Point3D(0, 0, 0.9),
+                Point3D.of(-0.9, 0, 0),
+                Point3D.of(0.9, 0, 0),
+                Point3D.of(0, -0.9, 0),
+                Point3D.of(0, 0.9, 0),
+                Point3D.of(0, 0, -0.9),
+                Point3D.of(0, 0, 0.9),
                 Point3D.ZERO);
     }
 
@@ -997,7 +997,7 @@ public void testBoolean_intersection() throws IOException {
         double size = 1.0;
         double radius = size * 0.5;
         PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_TOLERANCE);
-        PolyhedronsSet sphere = createSphere(new Point3D(size * 0.5, size * 0.5, size), radius, 8, 16);
+        PolyhedronsSet sphere = createSphere(Point3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
 
         // act
         PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Point3D>().intersection(box, sphere);
@@ -1012,20 +1012,20 @@ public void testBoolean_intersection() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, 0.5, 1.0),
-                new Point3D(1.1, 0.5, 1.0),
-                new Point3D(0.5, -0.1, 1.0),
-                new Point3D(0.5, 1.1, 1.0),
-                new Point3D(0.5, 0.5, 0.4),
-                new Point3D(0.5, 0.5, 1.1));
+                Point3D.of(-0.1, 0.5, 1.0),
+                Point3D.of(1.1, 0.5, 1.0),
+                Point3D.of(0.5, -0.1, 1.0),
+                Point3D.of(0.5, 1.1, 1.0),
+                Point3D.of(0.5, 0.5, 0.4),
+                Point3D.of(0.5, 0.5, 1.1));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.5, 0.9),
-                new Point3D(0.9, 0.5, 0.9),
-                new Point3D(0.5, 0.1, 0.9),
-                new Point3D(0.5, 0.9, 0.9),
-                new Point3D(0.5, 0.5, 0.6),
-                new Point3D(0.5, 0.5, 0.9));
+                Point3D.of(0.1, 0.5, 0.9),
+                Point3D.of(0.9, 0.5, 0.9),
+                Point3D.of(0.5, 0.1, 0.9),
+                Point3D.of(0.5, 0.9, 0.9),
+                Point3D.of(0.5, 0.5, 0.6),
+                Point3D.of(0.5, 0.5, 0.9));
     }
 
     @Test
@@ -1047,20 +1047,20 @@ public void testIntersection_self() {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-1.1, 0, 0),
-                new Point3D(1.1, 0, 0),
-                new Point3D(0, -1.1, 0),
-                new Point3D(0, 1.1, 0),
-                new Point3D(0, 0, -1.1),
-                new Point3D(0, 0, 1.1));
+                Point3D.of(-1.1, 0, 0),
+                Point3D.of(1.1, 0, 0),
+                Point3D.of(0, -1.1, 0),
+                Point3D.of(0, 1.1, 0),
+                Point3D.of(0, 0, -1.1),
+                Point3D.of(0, 0, 1.1));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(-0.9, 0, 0),
-                new Point3D(0.9, 0, 0),
-                new Point3D(0, -0.9, 0),
-                new Point3D(0, 0.9, 0),
-                new Point3D(0, 0, -0.9),
-                new Point3D(0, 0, 0.9),
+                Point3D.of(-0.9, 0, 0),
+                Point3D.of(0.9, 0, 0),
+                Point3D.of(0, -0.9, 0),
+                Point3D.of(0, 0.9, 0),
+                Point3D.of(0, 0, -0.9),
+                Point3D.of(0, 0, 0.9),
                 Point3D.ZERO);
     }
 
@@ -1090,21 +1090,21 @@ public void testBoolean_xor_twoCubes() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, -0.1, -0.1),
-                new Point3D(0.75, 0.75, 0.75),
-                new Point3D(1.6, 1.6, 1.6));
+                Point3D.of(-0.1, -0.1, -0.1),
+                Point3D.of(0.75, 0.75, 0.75),
+                Point3D.of(1.6, 1.6, 1.6));
 
         checkPoints(Region.Location.BOUNDARY, result,
-                new Point3D(0, 0, 0),
-                new Point3D(0.5, 0.5, 0.5),
-                new Point3D(1, 1, 1),
-                new Point3D(1.5, 1.5, 1.5));
+                Point3D.of(0, 0, 0),
+                Point3D.of(0.5, 0.5, 0.5),
+                Point3D.of(1, 1, 1),
+                Point3D.of(1.5, 1.5, 1.5));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.1, 0.1),
-                new Point3D(0.4, 0.4, 0.4),
-                new Point3D(1.1, 1.1, 1.1),
-                new Point3D(1.4, 1.4, 1.4));
+                Point3D.of(0.1, 0.1, 0.1),
+                Point3D.of(0.4, 0.4, 0.4),
+                Point3D.of(1.1, 1.1, 1.1),
+                Point3D.of(1.4, 1.4, 1.4));
     }
 
     @Test
@@ -1114,7 +1114,7 @@ public void testBoolean_xor_cubeAndSphere() throws IOException {
         double size = 1.0;
         double radius = size * 0.5;
         PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_TOLERANCE);
-        PolyhedronsSet sphere = createSphere(new Point3D(size * 0.5, size * 0.5, size), radius, 8, 16);
+        PolyhedronsSet sphere = createSphere(Point3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
 
         // act
         PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Point3D>().xor(box, sphere);
@@ -1130,21 +1130,21 @@ public void testBoolean_xor_cubeAndSphere() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, 0.5, 0.5),
-                new Point3D(1.1, 0.5, 0.5),
-                new Point3D(0.5, -0.1, 0.5),
-                new Point3D(0.5, 1.1, 0.5),
-                new Point3D(0.5, 0.5, -0.1),
-                new Point3D(0.5, 0.5, 1.6),
-                new Point3D(0.5, 0.5, 0.9));
+                Point3D.of(-0.1, 0.5, 0.5),
+                Point3D.of(1.1, 0.5, 0.5),
+                Point3D.of(0.5, -0.1, 0.5),
+                Point3D.of(0.5, 1.1, 0.5),
+                Point3D.of(0.5, 0.5, -0.1),
+                Point3D.of(0.5, 0.5, 1.6),
+                Point3D.of(0.5, 0.5, 0.9));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.5, 0.5),
-                new Point3D(0.9, 0.5, 0.5),
-                new Point3D(0.5, 0.1, 0.5),
-                new Point3D(0.5, 0.9, 0.5),
-                new Point3D(0.5, 0.5, 0.1),
-                new Point3D(0.5, 0.5, 1.4));
+                Point3D.of(0.1, 0.5, 0.5),
+                Point3D.of(0.9, 0.5, 0.5),
+                Point3D.of(0.5, 0.1, 0.5),
+                Point3D.of(0.5, 0.9, 0.5),
+                Point3D.of(0.5, 0.5, 0.1),
+                Point3D.of(0.5, 0.5, 1.4));
     }
 
     @Test
@@ -1165,18 +1165,18 @@ public void testXor_self() {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-1.1, 0, 0),
-                new Point3D(1.1, 0, 0),
-                new Point3D(0, -1.1, 0),
-                new Point3D(0, 1.1, 0),
-                new Point3D(0, 0, -1.1),
-                new Point3D(0, 0, 1.1),
-                new Point3D(-0.9, 0, 0),
-                new Point3D(0.9, 0, 0),
-                new Point3D(0, -0.9, 0),
-                new Point3D(0, 0.9, 0),
-                new Point3D(0, 0, -0.9),
-                new Point3D(0, 0, 0.9),
+                Point3D.of(-1.1, 0, 0),
+                Point3D.of(1.1, 0, 0),
+                Point3D.of(0, -1.1, 0),
+                Point3D.of(0, 1.1, 0),
+                Point3D.of(0, 0, -1.1),
+                Point3D.of(0, 0, 1.1),
+                Point3D.of(-0.9, 0, 0),
+                Point3D.of(0.9, 0, 0),
+                Point3D.of(0, -0.9, 0),
+                Point3D.of(0, 0.9, 0),
+                Point3D.of(0, 0, -0.9),
+                Point3D.of(0, 0, 0.9),
                 Point3D.ZERO);
     }
 
@@ -1187,7 +1187,7 @@ public void testBoolean_difference() throws IOException {
         double size = 1.0;
         double radius = size * 0.5;
         PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_TOLERANCE);
-        PolyhedronsSet sphere = createSphere(new Point3D(size * 0.5, size * 0.5, size), radius, 8, 16);
+        PolyhedronsSet sphere = createSphere(Point3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
 
         // act
         PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Point3D>().difference(box, sphere);
@@ -1202,20 +1202,20 @@ public void testBoolean_difference() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, 0.5, 1.0),
-                new Point3D(1.1, 0.5, 1.0),
-                new Point3D(0.5, -0.1, 1.0),
-                new Point3D(0.5, 1.1, 1.0),
-                new Point3D(0.5, 0.5, -0.1),
-                new Point3D(0.5, 0.5, 0.6));
+                Point3D.of(-0.1, 0.5, 1.0),
+                Point3D.of(1.1, 0.5, 1.0),
+                Point3D.of(0.5, -0.1, 1.0),
+                Point3D.of(0.5, 1.1, 1.0),
+                Point3D.of(0.5, 0.5, -0.1),
+                Point3D.of(0.5, 0.5, 0.6));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.5, 0.4),
-                new Point3D(0.9, 0.5, 0.4),
-                new Point3D(0.5, 0.1, 0.4),
-                new Point3D(0.5, 0.9, 0.4),
-                new Point3D(0.5, 0.5, 0.1),
-                new Point3D(0.5, 0.5, 0.4));
+                Point3D.of(0.1, 0.5, 0.4),
+                Point3D.of(0.9, 0.5, 0.4),
+                Point3D.of(0.5, 0.1, 0.4),
+                Point3D.of(0.5, 0.9, 0.4),
+                Point3D.of(0.5, 0.5, 0.1),
+                Point3D.of(0.5, 0.5, 0.4));
     }
 
     @Test
@@ -1236,18 +1236,18 @@ public void testDifference_self() {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-1.1, 0, 0),
-                new Point3D(1.1, 0, 0),
-                new Point3D(0, -1.1, 0),
-                new Point3D(0, 1.1, 0),
-                new Point3D(0, 0, -1.1),
-                new Point3D(0, 0, 1.1),
-                new Point3D(-0.9, 0, 0),
-                new Point3D(0.9, 0, 0),
-                new Point3D(0, -0.9, 0),
-                new Point3D(0, 0.9, 0),
-                new Point3D(0, 0, -0.9),
-                new Point3D(0, 0, 0.9),
+                Point3D.of(-1.1, 0, 0),
+                Point3D.of(1.1, 0, 0),
+                Point3D.of(0, -1.1, 0),
+                Point3D.of(0, 1.1, 0),
+                Point3D.of(0, 0, -1.1),
+                Point3D.of(0, 0, 1.1),
+                Point3D.of(-0.9, 0, 0),
+                Point3D.of(0.9, 0, 0),
+                Point3D.of(0, -0.9, 0),
+                Point3D.of(0, 0.9, 0),
+                Point3D.of(0, 0, -0.9),
+                Point3D.of(0, 0, 0.9),
                 Point3D.ZERO);
     }
 
@@ -1258,9 +1258,9 @@ public void testBoolean_multiple() throws IOException {
         double size = 1.0;
         double radius = size * 0.5;
         PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_TOLERANCE);
-        PolyhedronsSet sphereToAdd = createSphere(new Point3D(size * 0.5, size * 0.5, size), radius, 8, 16);
-        PolyhedronsSet sphereToRemove1 = createSphere(new Point3D(size * 0.5, 0, size * 0.5), radius, 8, 16);
-        PolyhedronsSet sphereToRemove2 = createSphere(new Point3D(size * 0.5, 1, size * 0.5), radius, 8, 16);
+        PolyhedronsSet sphereToAdd = createSphere(Point3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
+        PolyhedronsSet sphereToRemove1 = createSphere(Point3D.of(size * 0.5, 0, size * 0.5), radius, 8, 16);
+        PolyhedronsSet sphereToRemove2 = createSphere(Point3D.of(size * 0.5, 1, size * 0.5), radius, 8, 16);
 
         RegionFactory<Point3D> factory = new RegionFactory<Point3D>();
 
@@ -1280,20 +1280,20 @@ public void testBoolean_multiple() throws IOException {
         Assert.assertFalse(result.isFull());
 
         checkPoints(Region.Location.OUTSIDE, result,
-                new Point3D(-0.1, 0.5, 0.5),
-                new Point3D(1.1, 0.5, 0.5),
-                new Point3D(0.5, 0.4, 0.5),
-                new Point3D(0.5, 0.6, 0.5),
-                new Point3D(0.5, 0.5, -0.1),
-                new Point3D(0.5, 0.5, 1.6));
+                Point3D.of(-0.1, 0.5, 0.5),
+                Point3D.of(1.1, 0.5, 0.5),
+                Point3D.of(0.5, 0.4, 0.5),
+                Point3D.of(0.5, 0.6, 0.5),
+                Point3D.of(0.5, 0.5, -0.1),
+                Point3D.of(0.5, 0.5, 1.6));
 
         checkPoints(Region.Location.INSIDE, result,
-                new Point3D(0.1, 0.5, 0.1),
-                new Point3D(0.9, 0.5, 0.1),
-                new Point3D(0.5, 0.4, 0.1),
-                new Point3D(0.5, 0.6, 0.1),
-                new Point3D(0.5, 0.5, 0.1),
-                new Point3D(0.5, 0.5, 1.4));
+                Point3D.of(0.1, 0.5, 0.1),
+                Point3D.of(0.9, 0.5, 0.1),
+                Point3D.of(0.5, 0.4, 0.1),
+                Point3D.of(0.5, 0.6, 0.1),
+                Point3D.of(0.5, 0.5, 0.1),
+                Point3D.of(0.5, 0.5, 1.4));
     }
 
     @Test
@@ -1302,12 +1302,12 @@ public void testProjectToBoundary() {
         PolyhedronsSet polySet = new PolyhedronsSet(0, 1, 0, 1, 0, 1, TEST_TOLERANCE);
 
         // act/assert
-        checkProjectToBoundary(polySet, new Point3D(0.4, 0.5, 0.5),
-                new Point3D(0, 0.5, 0.5), -0.4);
-        checkProjectToBoundary(polySet, new Point3D(1.5, 0.5, 0.5),
-                new Point3D(1, 0.5, 0.5), 0.5);
-        checkProjectToBoundary(polySet, new Point3D(2, 2, 2),
-                new Point3D(1, 1, 1), Math.sqrt(3));
+        checkProjectToBoundary(polySet, Point3D.of(0.4, 0.5, 0.5),
+                Point3D.of(0, 0.5, 0.5), -0.4);
+        checkProjectToBoundary(polySet, Point3D.of(1.5, 0.5, 0.5),
+                Point3D.of(1, 0.5, 0.5), 0.5);
+        checkProjectToBoundary(polySet, Point3D.of(2, 2, 2),
+                Point3D.of(1, 1, 1), Math.sqrt(3));
     }
 
     @Test
@@ -1317,12 +1317,12 @@ public void testProjectToBoundary_invertedRegion() {
         polySet = (PolyhedronsSet) new RegionFactory<Point3D>().getComplement(polySet);
 
         // act/assert
-        checkProjectToBoundary(polySet, new Point3D(0.4, 0.5, 0.5),
-                new Point3D(0, 0.5, 0.5), 0.4);
-        checkProjectToBoundary(polySet, new Point3D(1.5, 0.5, 0.5),
-                new Point3D(1, 0.5, 0.5), -0.5);
-        checkProjectToBoundary(polySet, new Point3D(2, 2, 2),
-                new Point3D(1, 1, 1), -Math.sqrt(3));
+        checkProjectToBoundary(polySet, Point3D.of(0.4, 0.5, 0.5),
+                Point3D.of(0, 0.5, 0.5), 0.4);
+        checkProjectToBoundary(polySet, Point3D.of(1.5, 0.5, 0.5),
+                Point3D.of(1, 0.5, 0.5), -0.5);
+        checkProjectToBoundary(polySet, Point3D.of(2, 2, 2),
+                Point3D.of(1, 1, 1), -Math.sqrt(3));
     }
 
     private void checkProjectToBoundary(PolyhedronsSet poly, Point3D toProject,
@@ -1356,54 +1356,54 @@ private void checkPoints(Region.Location expected, PolyhedronsSet poly, Point3D
 
         double offset = size * 0.5;
 
-        Plane xMinus = new Plane(center.add(new Vector3D(-offset, 0, 0)), Vector3D.MINUS_X, tolerance);
-        Plane xPlus = new Plane(center.add(new Vector3D(offset, 0, 0)), Vector3D.PLUS_X, tolerance);
-        Plane yPlus = new Plane(center.add(new Vector3D(0, offset, 0)), Vector3D.PLUS_Y, tolerance);
-        Plane yMinus = new Plane(center.add(new Vector3D(0, -offset, 0)), Vector3D.MINUS_Y, tolerance);
-        Plane zPlus = new Plane(center.add(new Vector3D(0, 0, offset)), Vector3D.PLUS_Z, tolerance);
-        Plane zMinus = new Plane(center.add(new Vector3D(0, 0, -offset)), Vector3D.MINUS_Z, tolerance);
+        Plane xMinus = new Plane(center.add(Vector3D.of(-offset, 0, 0)), Vector3D.MINUS_X, tolerance);
+        Plane xPlus = new Plane(center.add(Vector3D.of(offset, 0, 0)), Vector3D.PLUS_X, tolerance);
+        Plane yPlus = new Plane(center.add(Vector3D.of(0, offset, 0)), Vector3D.PLUS_Y, tolerance);
+        Plane yMinus = new Plane(center.add(Vector3D.of(0, -offset, 0)), Vector3D.MINUS_Y, tolerance);
+        Plane zPlus = new Plane(center.add(Vector3D.of(0, 0, offset)), Vector3D.PLUS_Z, tolerance);
+        Plane zMinus = new Plane(center.add(Vector3D.of(0, 0, -offset)), Vector3D.MINUS_Z, tolerance);
 
         // +x
         boundaries.add(createSubPlane(xPlus,
-                        center.add(new Vector3D(offset, offset, offset)),
-                        center.add(new Vector3D(offset, -offset, offset)),
-                        center.add(new Vector3D(offset, -offset, -offset)),
-                        center.add(new Vector3D(offset, offset, -offset))));
+                        center.add(Vector3D.of(offset, offset, offset)),
+                        center.add(Vector3D.of(offset, -offset, offset)),
+                        center.add(Vector3D.of(offset, -offset, -offset)),
+                        center.add(Vector3D.of(offset, offset, -offset))));
 
         // -x
         boundaries.add(createSubPlane(xMinus,
-                        center.add(new Vector3D(-offset, -offset, offset)),
-                        center.add(new Vector3D(-offset, offset, offset)),
-                        center.add(new Vector3D(-offset, offset, -offset)),
-                        center.add(new Vector3D(-offset, -offset, -offset))));
+                        center.add(Vector3D.of(-offset, -offset, offset)),
+                        center.add(Vector3D.of(-offset, offset, offset)),
+                        center.add(Vector3D.of(-offset, offset, -offset)),
+                        center.add(Vector3D.of(-offset, -offset, -offset))));
 
         // +y
         boundaries.add(createSubPlane(yPlus,
-                        center.add(new Vector3D(-offset, offset, offset)),
-                        center.add(new Vector3D(offset, offset, offset)),
-                        center.add(new Vector3D(offset, offset, -offset)),
-                        center.add(new Vector3D(-offset, offset, -offset))));
+                        center.add(Vector3D.of(-offset, offset, offset)),
+                        center.add(Vector3D.of(offset, offset, offset)),
+                        center.add(Vector3D.of(offset, offset, -offset)),
+                        center.add(Vector3D.of(-offset, offset, -offset))));
 
         // -y
         boundaries.add(createSubPlane(yMinus,
-                        center.add(new Vector3D(-offset, -offset, offset)),
-                        center.add(new Vector3D(-offset, -offset, -offset)),
-                        center.add(new Vector3D(offset, -offset, -offset)),
-                        center.add(new Vector3D(offset, -offset, offset))));
+                        center.add(Vector3D.of(-offset, -offset, offset)),
+                        center.add(Vector3D.of(-offset, -offset, -offset)),
+                        center.add(Vector3D.of(offset, -offset, -offset)),
+                        center.add(Vector3D.of(offset, -offset, offset))));
 
         // +z
         boundaries.add(createSubPlane(zPlus,
-                        center.add(new Vector3D(-offset, -offset, offset)),
-                        center.add(new Vector3D(offset, -offset, offset)),
-                        center.add(new Vector3D(offset, offset, offset)),
-                        center.add(new Vector3D(-offset, offset, offset))));
+                        center.add(Vector3D.of(-offset, -offset, offset)),
+                        center.add(Vector3D.of(offset, -offset, offset)),
+                        center.add(Vector3D.of(offset, offset, offset)),
+                        center.add(Vector3D.of(-offset, offset, offset))));
 
         // -z
         boundaries.add(createSubPlane(zMinus,
-                        center.add(new Vector3D(-offset, -offset, -offset)),
-                        center.add(new Vector3D(-offset, offset, -offset)),
-                        center.add(new Vector3D(offset, offset, -offset)),
-                        center.add(new Vector3D(offset, -offset, -offset))));
+                        center.add(Vector3D.of(-offset, -offset, -offset)),
+                        center.add(Vector3D.of(-offset, offset, -offset)),
+                        center.add(Vector3D.of(offset, offset, -offset)),
+                        center.add(Vector3D.of(offset, -offset, -offset))));
 
         return boundaries;
     }
@@ -1423,8 +1423,8 @@ private PolyhedronsSet createSphere(Point3D center, double radius, int stacks, i
         List<Plane> planes = new ArrayList<>();
 
         // add top and bottom planes (+/- z)
-        Point3D topZ = new Point3D(center.getX(), center.getY(), center.getZ() + radius);
-        Point3D bottomZ = new Point3D(center.getX(), center.getY(), center.getZ() - radius);
+        Point3D topZ = Point3D.of(center.getX(), center.getY(), center.getZ() + radius);
+        Point3D bottomZ = Point3D.of(center.getX(), center.getY(), center.getZ() - radius);
 
         planes.add(new Plane(topZ, Vector3D.PLUS_Z, TEST_TOLERANCE));
         planes.add(new Plane(bottomZ, Vector3D.MINUS_Z, TEST_TOLERANCE));
@@ -1457,7 +1457,7 @@ private PolyhedronsSet createSphere(Point3D center, double radius, int stacks, i
                 x = Math.cos(hAngle) * stackRadius;
                 y = Math.sin(hAngle) * stackRadius;
 
-                norm = new Vector3D(x, y, stackHeight).normalize();
+                norm = Vector3D.of(x, y, stackHeight).normalize();
                 pt = center.add(norm.scalarMultiply(adjustedRadius));
 
                 planes.add(new Plane(pt, norm, TEST_TOLERANCE));
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
index 875575b..ca28f44 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
@@ -51,22 +51,22 @@ public void testIdentity() {
   @Deprecated
   public void testAxisAngleDeprecated() {
 
-    Rotation r = new Rotation(new Vector3D(10, 10, 10), 2 * Math.PI / 3);
+    Rotation r = new Rotation(Vector3D.of(10, 10, 10), 2 * Math.PI / 3);
     checkVector(r.applyTo(Vector3D.PLUS_X), Vector3D.PLUS_Y);
     checkVector(r.applyTo(Vector3D.PLUS_Y), Vector3D.PLUS_Z);
     checkVector(r.applyTo(Vector3D.PLUS_Z), Vector3D.PLUS_X);
     double s = 1 / Math.sqrt(3);
-    checkVector(r.getAxis(), new Vector3D(s, s, s));
+    checkVector(r.getAxis(), Vector3D.of(s, s, s));
     checkAngle(r.getAngle(), 2 * Math.PI / 3);
 
     try {
-      new Rotation(new Vector3D(0, 0, 0), 2 * Math.PI / 3);
+      new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3);
       Assert.fail("an exception should have been thrown");
     } catch (IllegalArgumentException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI);
-    checkVector(r.getAxis(), new Vector3D(0, 0, -1));
+    checkVector(r.getAxis(), Vector3D.of(0, 0, -1));
     checkAngle(r.getAngle(), 0.5 * Math.PI);
 
     r = new Rotation(Vector3D.PLUS_Y, Math.PI);
@@ -80,24 +80,24 @@ public void testAxisAngleDeprecated() {
   @Test
   public void testAxisAngleVectorOperator() {
 
-    Rotation r = new Rotation(new Vector3D(10, 10, 10), 2 * Math.PI / 3, RotationConvention.VECTOR_OPERATOR);
+    Rotation r = new Rotation(Vector3D.of(10, 10, 10), 2 * Math.PI / 3, RotationConvention.VECTOR_OPERATOR);
     checkVector(r.applyTo(Vector3D.PLUS_X), Vector3D.PLUS_Y);
     checkVector(r.applyTo(Vector3D.PLUS_Y), Vector3D.PLUS_Z);
     checkVector(r.applyTo(Vector3D.PLUS_Z), Vector3D.PLUS_X);
     double s = 1 / Math.sqrt(3);
-    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), new Vector3D( s,  s,  s));
-    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), new Vector3D(-s, -s, -s));
+    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), Vector3D.of( s,  s,  s));
+    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), Vector3D.of(-s, -s, -s));
     checkAngle(r.getAngle(), 2 * Math.PI / 3);
 
     try {
-      new Rotation(new Vector3D(0, 0, 0), 2 * Math.PI / 3, RotationConvention.VECTOR_OPERATOR);
+      new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3, RotationConvention.VECTOR_OPERATOR);
       Assert.fail("an exception should have been thrown");
     } catch (IllegalArgumentException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI, RotationConvention.VECTOR_OPERATOR);
-    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), new Vector3D(0, 0, -1));
-    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), new Vector3D(0, 0, +1));
+    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), Vector3D.of(0, 0, -1));
+    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), Vector3D.of(0, 0, +1));
     checkAngle(r.getAngle(), 0.5 * Math.PI);
 
     r = new Rotation(Vector3D.PLUS_Y, Math.PI, RotationConvention.VECTOR_OPERATOR);
@@ -113,24 +113,24 @@ public void testAxisAngleVectorOperator() {
   @Test
   public void testAxisAngleFrameTransform() {
 
-    Rotation r = new Rotation(new Vector3D(10, 10, 10), 2 * Math.PI / 3, RotationConvention.FRAME_TRANSFORM);
+    Rotation r = new Rotation(Vector3D.of(10, 10, 10), 2 * Math.PI / 3, RotationConvention.FRAME_TRANSFORM);
     checkVector(r.applyTo(Vector3D.PLUS_X), Vector3D.PLUS_Z);
     checkVector(r.applyTo(Vector3D.PLUS_Y), Vector3D.PLUS_X);
     checkVector(r.applyTo(Vector3D.PLUS_Z), Vector3D.PLUS_Y);
     double s = 1 / Math.sqrt(3);
-    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), new Vector3D( s,  s,  s));
-    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), new Vector3D(-s, -s, -s));
+    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), Vector3D.of( s,  s,  s));
+    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), Vector3D.of(-s, -s, -s));
     checkAngle(r.getAngle(), 2 * Math.PI / 3);
 
     try {
-      new Rotation(new Vector3D(0, 0, 0), 2 * Math.PI / 3, RotationConvention.FRAME_TRANSFORM);
+      new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3, RotationConvention.FRAME_TRANSFORM);
       Assert.fail("an exception should have been thrown");
     } catch (IllegalArgumentException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI, RotationConvention.FRAME_TRANSFORM);
-    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), new Vector3D(0, 0, -1));
-    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), new Vector3D(0, 0, +1));
+    checkVector(r.getAxis(RotationConvention.FRAME_TRANSFORM), Vector3D.of(0, 0, -1));
+    checkVector(r.getAxis(RotationConvention.VECTOR_OPERATOR), Vector3D.of(0, 0, +1));
     checkAngle(r.getAngle(), 0.5 * Math.PI);
 
     r = new Rotation(Vector3D.PLUS_Y, Math.PI, RotationConvention.FRAME_TRANSFORM);
@@ -185,8 +185,8 @@ public void testRevertFrameTransform() {
   @Test
   public void testVectorOnePair() {
 
-    Vector3D u = new Vector3D(3, 2, 1);
-    Vector3D v = new Vector3D(-4, 2, 2);
+    Vector3D u = Vector3D.of(3, 2, 1);
+    Vector3D v = Vector3D.of(-4, 2, 2);
     Rotation r = new Rotation(u, v);
     checkVector(r.applyTo(u.scalarMultiply(v.getNorm())), v.scalarMultiply(u.getNorm()));
 
@@ -204,10 +204,10 @@ public void testVectorOnePair() {
   @Test
   public void testVectorTwoPairs() {
 
-    Vector3D u1 = new Vector3D(3, 0, 0);
-    Vector3D u2 = new Vector3D(0, 5, 0);
-    Vector3D v1 = new Vector3D(0, 0, 2);
-    Vector3D v2 = new Vector3D(-2, 0, 2);
+    Vector3D u1 = Vector3D.of(3, 0, 0);
+    Vector3D u2 = Vector3D.of(0, 5, 0);
+    Vector3D v1 = Vector3D.of(0, 0, 2);
+    Vector3D v2 = Vector3D.of(-2, 0, 2);
     Rotation r = new Rotation(u1, u2, v1, v2);
     checkVector(r.applyTo(Vector3D.PLUS_X), Vector3D.PLUS_Z);
     checkVector(r.applyTo(Vector3D.PLUS_Y), Vector3D.MINUS_X);
@@ -223,8 +223,8 @@ public void testVectorTwoPairs() {
 
     double sqrt = Math.sqrt(2) / 2;
     r = new Rotation(Vector3D.PLUS_X,  Vector3D.PLUS_Y,
-                     new Vector3D(0.5, 0.5,  sqrt),
-                     new Vector3D(0.5, 0.5, -sqrt));
+                     Vector3D.of(0.5, 0.5,  sqrt),
+                     Vector3D.of(0.5, 0.5, -sqrt));
     checkRotation(r, sqrt, 0.5, 0.5, 0);
 
     r = new Rotation(u1, u2, u1, u1.crossProduct(u2));
@@ -362,11 +362,11 @@ public void testMatrix() {
     }
 
     checkVector(r.applyTo(Vector3D.PLUS_X),
-                new Vector3D(m3[0][0], m3[1][0], m3[2][0]));
+                Vector3D.of(m3[0][0], m3[1][0], m3[2][0]));
     checkVector(r.applyTo(Vector3D.PLUS_Y),
-                new Vector3D(m3[0][1], m3[1][1], m3[2][1]));
+                Vector3D.of(m3[0][1], m3[1][1], m3[2][1]));
     checkVector(r.applyTo(Vector3D.PLUS_Z),
-                new Vector3D(m3[0][2], m3[1][2], m3[2][2]));
+                Vector3D.of(m3[0][2], m3[1][2], m3[2][2]));
 
     double[][] m4 = { { 1.0,  0.0,  0.0 },
                       { 0.0, -1.0,  0.0 },
@@ -524,7 +524,7 @@ public void testSingularities() {
   @Test
   public void testQuaternion() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
     double n = 23.5;
     Rotation r2 = new Rotation(n * r1.getQ0(), n * r1.getQ1(),
                                n * r1.getQ2(), n * r1.getQ3(),
@@ -532,7 +532,7 @@ public void testQuaternion() {
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r2.applyTo(u), r1.applyTo(u));
         }
       }
@@ -546,14 +546,14 @@ public void testQuaternion() {
   @Test
   public void testApplyTo() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
     Rotation r3 = r2.applyTo(r1);
 
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r2.applyTo(r1.applyTo(u)), r3.applyTo(u));
         }
       }
@@ -564,14 +564,14 @@ public void testApplyTo() {
   @Test
   public void testComposeVectorOperator() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
     Rotation r3 = r2.compose(r1, RotationConvention.VECTOR_OPERATOR);
 
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r2.applyTo(r1.applyTo(u)), r3.applyTo(u));
         }
       }
@@ -582,8 +582,8 @@ public void testComposeVectorOperator() {
   @Test
   public void testComposeFrameTransform() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.FRAME_TRANSFORM);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.FRAME_TRANSFORM);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.FRAME_TRANSFORM);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.FRAME_TRANSFORM);
     Rotation r3 = r2.compose(r1, RotationConvention.FRAME_TRANSFORM);
     Rotation r4 = r1.compose(r2, RotationConvention.VECTOR_OPERATOR);
     Assert.assertEquals(0.0, Rotation.distance(r3, r4), 1.0e-15);
@@ -591,7 +591,7 @@ public void testComposeFrameTransform() {
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r1.applyTo(r2.applyTo(u)), r3.applyTo(u));
         }
       }
@@ -602,14 +602,14 @@ public void testComposeFrameTransform() {
   @Test
   public void testApplyInverseToRotation() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
     Rotation r3 = r2.applyInverseTo(r1);
 
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r2.applyInverseTo(r1.applyTo(u)), r3.applyTo(u));
         }
       }
@@ -620,14 +620,14 @@ public void testApplyInverseToRotation() {
   @Test
   public void testComposeInverseVectorOperator() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.VECTOR_OPERATOR);
     Rotation r3 = r2.composeInverse(r1, RotationConvention.VECTOR_OPERATOR);
 
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r2.applyInverseTo(r1.applyTo(u)), r3.applyTo(u));
         }
       }
@@ -638,8 +638,8 @@ public void testComposeInverseVectorOperator() {
   @Test
   public void testComposeInverseFrameTransform() {
 
-    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.FRAME_TRANSFORM);
-    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3, RotationConvention.FRAME_TRANSFORM);
+    Rotation r1 = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.FRAME_TRANSFORM);
+    Rotation r2 = new Rotation(Vector3D.of(-1, 3, 2), 0.3, RotationConvention.FRAME_TRANSFORM);
     Rotation r3 = r2.composeInverse(r1, RotationConvention.FRAME_TRANSFORM);
     Rotation r4 = r1.revert().composeInverse(r2.revert(), RotationConvention.VECTOR_OPERATOR);
     Assert.assertEquals(0.0, Rotation.distance(r3, r4), 1.0e-15);
@@ -647,7 +647,7 @@ public void testComposeInverseFrameTransform() {
     for (double x = -0.9; x < 0.9; x += 0.2) {
       for (double y = -0.9; y < 0.9; y += 0.2) {
         for (double z = -0.9; z < 0.9; z += 0.2) {
-          Vector3D u = new Vector3D(x, y, z);
+          Vector3D u = Vector3D.of(x, y, z);
           checkVector(r1.applyTo(r2.applyInverseTo(u)), r3.applyTo(u));
         }
       }
@@ -658,12 +658,12 @@ public void testComposeInverseFrameTransform() {
   @Test
   public void testArray() {
 
-      Rotation r = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+      Rotation r = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
 
       for (double x = -0.9; x < 0.9; x += 0.2) {
           for (double y = -0.9; y < 0.9; y += 0.2) {
               for (double z = -0.9; z < 0.9; z += 0.2) {
-                  Vector3D u = new Vector3D(x, y, z);
+                  Vector3D u = Vector3D.of(x, y, z);
                   Vector3D v = r.applyTo(u);
                   double[] inOut = new double[] { x, y, z };
                   r.applyTo(inOut, inOut);
@@ -683,10 +683,10 @@ public void testArray() {
   @Test
   public void testApplyInverseTo() {
 
-    Rotation r = new Rotation(new Vector3D(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
+    Rotation r = new Rotation(Vector3D.of(2, -3, 5), 1.7, RotationConvention.VECTOR_OPERATOR);
     for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
       for (double phi = -1.55; phi < 1.55; phi += 0.2) {
-          Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi),
+          Vector3D u = Vector3D.of(Math.cos(lambda) * Math.cos(phi),
                                     Math.sin(lambda) * Math.cos(phi),
                                     Math.sin(phi));
           r.applyInverseTo(r.applyTo(u));
@@ -698,7 +698,7 @@ public void testApplyInverseTo() {
     r = Rotation.IDENTITY;
     for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
       for (double phi = -1.55; phi < 1.55; phi += 0.2) {
-          Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi),
+          Vector3D u = Vector3D.of(Math.cos(lambda) * Math.cos(phi),
                                     Math.sin(lambda) * Math.cos(phi),
                                     Math.sin(phi));
           checkVector(u, r.applyInverseTo(r.applyTo(u)));
@@ -709,7 +709,7 @@ public void testApplyInverseTo() {
     r = new Rotation(Vector3D.PLUS_Z, Math.PI, RotationConvention.VECTOR_OPERATOR);
     for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
       for (double phi = -1.55; phi < 1.55; phi += 0.2) {
-          Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi),
+          Vector3D u = Vector3D.of(Math.cos(lambda) * Math.cos(phi),
                                     Math.sin(lambda) * Math.cos(phi),
                                     Math.sin(phi));
           checkVector(u, r.applyInverseTo(r.applyTo(u)));
@@ -721,10 +721,10 @@ public void testApplyInverseTo() {
 
   @Test
   public void testIssue639() {
-      Vector3D u1 = new Vector3D(-1321008684645961.0 /  268435456.0,
+      Vector3D u1 = Vector3D.of(-1321008684645961.0 /  268435456.0,
                                  -5774608829631843.0 /  268435456.0,
                                  -3822921525525679.0 / 4294967296.0);
-      Vector3D u2 =new Vector3D( -5712344449280879.0 /    2097152.0,
+      Vector3D u2 =Vector3D.of( -5712344449280879.0 /    2097152.0,
                                  -2275058564560979.0 /    1048576.0,
                                   4423475992255071.0 /      65536.0);
       Rotation rot = new Rotation(u1, u2, Vector3D.PLUS_X,Vector3D.PLUS_Z);
@@ -736,11 +736,11 @@ public void testIssue639() {
 
   @Test
   public void testIssue801() {
-      Vector3D u1 = new Vector3D(0.9999988431610581, -0.0015210774290851095, 0.0);
-      Vector3D u2 = new Vector3D(0.0, 0.0, 1.0);
+      Vector3D u1 = Vector3D.of(0.9999988431610581, -0.0015210774290851095, 0.0);
+      Vector3D u2 = Vector3D.of(0.0, 0.0, 1.0);
 
-      Vector3D v1 = new Vector3D(0.9999999999999999, 0.0, 0.0);
-      Vector3D v2 = new Vector3D(0.0, 0.0, -1.0);
+      Vector3D v1 = Vector3D.of(0.9999999999999999, 0.0, 0.0);
+      Vector3D v2 = Vector3D.of(0.0, 0.0, -1.0);
 
       Rotation quat = new Rotation(u1, u2, v1, v2);
       double q2 = quat.getQ0() * quat.getQ0() +
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLineTest.java
index c076b9e..49e31a2 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLineTest.java
@@ -28,19 +28,19 @@
 
     @Test
     public void testEndPoints() {
-        Point3D p1 = new Point3D(-1, -7, 2);
-        Point3D p2 = new Point3D(7, -1, 0);
+        Point3D p1 = Point3D.of(-1, -7, 2);
+        Point3D p2 = Point3D.of(7, -1, 0);
         Segment segment = new Segment(p1, p2, new Line(p1, p2, 1.0e-10));
         SubLine sub = new SubLine(segment);
         List<Segment> segments = sub.getSegments();
         Assert.assertEquals(1, segments.size());
-        Assert.assertEquals(0.0, new Point3D(-1, -7, 2).distance(segments.get(0).getStart()), 1.0e-10);
-        Assert.assertEquals(0.0, new Point3D( 7, -1, 0).distance(segments.get(0).getEnd()), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(-1, -7, 2).distance(segments.get(0).getStart()), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of( 7, -1, 0).distance(segments.get(0).getEnd()), 1.0e-10);
     }
 
     @Test
     public void testNoEndPoints() {
-        SubLine wholeLine = new Line(new Point3D(-1, 7, 2), new Point3D(7, 1, 0), 1.0e-10).wholeLine();
+        SubLine wholeLine = new Line(Point3D.of(-1, 7, 2), Point3D.of(7, 1, 0), 1.0e-10).wholeLine();
         List<Segment> segments = wholeLine.getSegments();
         Assert.assertEquals(1, segments.size());
         Assert.assertTrue(Double.isInfinite(segments.get(0).getStart().getX()) &&
@@ -59,7 +59,7 @@ public void testNoEndPoints() {
 
     @Test
     public void testNoSegments() {
-        SubLine empty = new SubLine(new Line(new Point3D(-1, -7, 2), new Point3D(7, -1, 0), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point3D.of(-1, -7, 2), Point3D.of(7, -1, 0), 1.0e-10),
                                     (IntervalsSet) new RegionFactory<Point1D>().getComplement(new IntervalsSet(1.0e-10)));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(0, segments.size());
@@ -67,7 +67,7 @@ public void testNoSegments() {
 
     @Test
     public void testSeveralSegments() {
-        SubLine twoSubs = new SubLine(new Line(new Point3D(-1, -7, 2), new Point3D(7, -1, 0), 1.0e-10),
+        SubLine twoSubs = new SubLine(new Line(Point3D.of(-1, -7, 2), Point3D.of(7, -1, 0), 1.0e-10),
                                       (IntervalsSet) new RegionFactory<Point1D>().union(new IntervalsSet(1, 2, 1.0e-10),
                                                                                             new IntervalsSet(3, 4, 1.0e-10)));
         List<Segment> segments = twoSubs.getSegments();
@@ -76,7 +76,7 @@ public void testSeveralSegments() {
 
     @Test
     public void testHalfInfiniteNeg() {
-        SubLine empty = new SubLine(new Line(new Point3D(-1, -7, 2), new Point3D(7, -1, -2), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point3D.of(-1, -7, 2), Point3D.of(7, -1, -2), 1.0e-10),
                                     new IntervalsSet(Double.NEGATIVE_INFINITY, 0.0, 1.0e-10));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(1, segments.size());
@@ -86,16 +86,16 @@ public void testHalfInfiniteNeg() {
                           segments.get(0).getStart().getY() < 0);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getStart().getZ()) &&
                           segments.get(0).getStart().getZ() > 0);
-        Assert.assertEquals(0.0, new Point3D(3, -4, 0).distance(segments.get(0).getEnd()), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(3, -4, 0).distance(segments.get(0).getEnd()), 1.0e-10);
     }
 
     @Test
     public void testHalfInfinitePos() {
-        SubLine empty = new SubLine(new Line(new Point3D(-1, -7, 2), new Point3D(7, -1, -2), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point3D.of(-1, -7, 2), Point3D.of(7, -1, -2), 1.0e-10),
                                     new IntervalsSet(0.0, Double.POSITIVE_INFINITY, 1.0e-10));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(1, segments.size());
-        Assert.assertEquals(0.0, new Point3D(3, -4, 0).distance(segments.get(0).getStart()), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(3, -4, 0).distance(segments.get(0).getStart()), 1.0e-10);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getEnd().getX()) &&
                           segments.get(0).getEnd().getX() > 0);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getEnd().getY()) &&
@@ -106,56 +106,56 @@ public void testHalfInfinitePos() {
 
     @Test
     public void testIntersectionInsideInside() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(3, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 2, 2), 1.0e-10);
-        Assert.assertEquals(0.0, new Point3D(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
-        Assert.assertEquals(0.0, new Point3D(2, 1, 1).distance(sub1.intersection(sub2, false)), 1.0e-12);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(3, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 2, 2), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        Assert.assertEquals(0.0, Point3D.of(2, 1, 1).distance(sub1.intersection(sub2, false)), 1.0e-12);
     }
 
     @Test
     public void testIntersectionInsideBoundary() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(3, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 1, 1), 1.0e-10);
-        Assert.assertEquals(0.0, new Point3D(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(3, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 1, 1), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionInsideOutside() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(3, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 0.5, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(3, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 0.5, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionBoundaryBoundary() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(2, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 1, 1), 1.0e-10);
-        Assert.assertEquals(0.0, new Point3D(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(2, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 1, 1), 1.0e-10);
+        Assert.assertEquals(0.0, Point3D.of(2, 1, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionBoundaryOutside() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(2, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 0.5, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(2, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 0.5, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionOutsideOutside() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(1.5, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 0, 0), new Point3D(2, 0.5, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(1.5, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 0, 0), Point3D.of(2, 0.5, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionNotIntersecting() {
-        SubLine sub1 = new SubLine(new Point3D(1, 1, 1), new Point3D(1.5, 1, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point3D(2, 3, 0), new Point3D(2, 3, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point3D.of(1, 1, 1), Point3D.of(1.5, 1, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point3D.of(2, 3, 0), Point3D.of(2, 3, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
index f2b4fcc..c4017bd 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
@@ -618,7 +618,7 @@ public void testEquals() {
     public void testToString() {
         // arrange
         Vector3D v = Vector3D.of(1, 2, 3);
-        Pattern pattern = Pattern.compile("\\{1.{0,2}; 2.{0,2}; 3.{0,2}\\}");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)");
 
         // act
         String str = v.toString();
@@ -628,6 +628,26 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector3D.parse("(1, 2, 3)"), 1, 2, 3);
+        checkVector(Vector3D.parse("(-1, -2, -3)"), -1, -2, -3);
+
+        checkVector(Vector3D.parse("(0.01, -1e-3, 0)"), 1e-2, -1e-3, 0);
+
+        checkVector(Vector3D.parse("(NaN, -Infinity, Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+        checkVector(Vector3D.parse(Vector3D.ZERO.toString()), 0, 0, 0);
+        checkVector(Vector3D.parse(Vector3D.MINUS_X.toString()), -1, 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector3D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java
index b8c7238..e3d5127 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java
@@ -1,5 +1,7 @@
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.util.regex.Pattern;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -68,6 +70,20 @@ public void testInfinite() {
         Assert.assertFalse(new StubCartesian2D(Double.NaN, Double.POSITIVE_INFINITY).isInfinite());
     }
 
+    @Test
+    public void testToString() {
+        // arrange
+        StubCartesian2D c = new StubCartesian2D(1, 2);
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
+
+        // act
+        String str = c.toString();
+
+        // assert
+        Assert.assertTrue("Expected string " + str + " to match regex " + pattern,
+                    pattern.matcher(str).matches());
+    }
+
     private static class StubCartesian2D extends Cartesian2D {
         private static final long serialVersionUID = 1L;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineTest.java
index 60ea0ad..f1bcebf 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineTest.java
@@ -25,49 +25,49 @@
 
     @Test
     public void testContains() {
-        Line l = new Line(new Point2D(0, 1), new Point2D(1, 2), 1.0e-10);
-        Assert.assertTrue(l.contains(new Point2D(0, 1)));
-        Assert.assertTrue(l.contains(new Point2D(1, 2)));
-        Assert.assertTrue(l.contains(new Point2D(7, 8)));
-        Assert.assertTrue(! l.contains(new Point2D(8, 7)));
+        Line l = new Line(Point2D.of(0, 1), Point2D.of(1, 2), 1.0e-10);
+        Assert.assertTrue(l.contains(Point2D.of(0, 1)));
+        Assert.assertTrue(l.contains(Point2D.of(1, 2)));
+        Assert.assertTrue(l.contains(Point2D.of(7, 8)));
+        Assert.assertTrue(! l.contains(Point2D.of(8, 7)));
     }
 
     @Test
     public void testAbscissa() {
-        Line l = new Line(new Point2D(2, 1), new Point2D(-2, -2), 1.0e-10);
+        Line l = new Line(Point2D.of(2, 1), Point2D.of(-2, -2), 1.0e-10);
         Assert.assertEquals(0.0,
-                            (l.toSubSpace(new Point2D(-3,  4))).getX(),
+                            (l.toSubSpace(Point2D.of(-3,  4))).getX(),
                             1.0e-10);
         Assert.assertEquals(0.0,
-                            (l.toSubSpace(new Point2D( 3, -4))).getX(),
+                            (l.toSubSpace(Point2D.of( 3, -4))).getX(),
                             1.0e-10);
         Assert.assertEquals(-5.0,
-                            (l.toSubSpace(new Point2D( 7, -1))).getX(),
+                            (l.toSubSpace(Point2D.of( 7, -1))).getX(),
                             1.0e-10);
         Assert.assertEquals(5.0,
-                             (l.toSubSpace(new Point2D(-1, -7))).getX(),
+                             (l.toSubSpace(Point2D.of(-1, -7))).getX(),
                              1.0e-10);
     }
 
     @Test
     public void testOffset() {
-        Line l = new Line(new Point2D(2, 1), new Point2D(-2, -2), 1.0e-10);
-        Assert.assertEquals(-5.0, l.getOffset(new Point2D(5, -3)), 1.0e-10);
-        Assert.assertEquals(+5.0, l.getOffset(new Point2D(-5, 2)), 1.0e-10);
+        Line l = new Line(Point2D.of(2, 1), Point2D.of(-2, -2), 1.0e-10);
+        Assert.assertEquals(-5.0, l.getOffset(Point2D.of(5, -3)), 1.0e-10);
+        Assert.assertEquals(+5.0, l.getOffset(Point2D.of(-5, 2)), 1.0e-10);
     }
 
     @Test
     public void testDistance() {
-        Line l = new Line(new Point2D(2, 1), new Point2D(-2, -2), 1.0e-10);
-        Assert.assertEquals(+5.0, l.distance(new Point2D(5, -3)), 1.0e-10);
-        Assert.assertEquals(+5.0, l.distance(new Point2D(-5, 2)), 1.0e-10);
+        Line l = new Line(Point2D.of(2, 1), Point2D.of(-2, -2), 1.0e-10);
+        Assert.assertEquals(+5.0, l.distance(Point2D.of(5, -3)), 1.0e-10);
+        Assert.assertEquals(+5.0, l.distance(Point2D.of(-5, 2)), 1.0e-10);
     }
 
     @Test
     public void testPointAt() {
-        Line l = new Line(new Point2D(2, 1), new Point2D(-2, -2), 1.0e-10);
+        Line l = new Line(Point2D.of(2, 1), Point2D.of(-2, -2), 1.0e-10);
         for (double a = -2.0; a < 2.0; a += 0.2) {
-            Point1D pA = new Point1D(a);
+            Point1D pA = Point1D.of(a);
             Point2D point = l.toSpace(pA);
             Assert.assertEquals(a, (l.toSubSpace(point)).getX(), 1.0e-10);
             Assert.assertEquals(0.0, l.getOffset(point),   1.0e-10);
@@ -81,34 +81,34 @@ public void testPointAt() {
 
     @Test
     public void testOriginOffset() {
-        Line l1 = new Line(new Point2D(0, 1), new Point2D(1, 2), 1.0e-10);
+        Line l1 = new Line(Point2D.of(0, 1), Point2D.of(1, 2), 1.0e-10);
         Assert.assertEquals(Math.sqrt(0.5), l1.getOriginOffset(), 1.0e-10);
-        Line l2 = new Line(new Point2D(1, 2), new Point2D(0, 1), 1.0e-10);
+        Line l2 = new Line(Point2D.of(1, 2), Point2D.of(0, 1), 1.0e-10);
         Assert.assertEquals(-Math.sqrt(0.5), l2.getOriginOffset(), 1.0e-10);
     }
 
     @Test
     public void testParallel() {
-        Line l1 = new Line(new Point2D(0, 1), new Point2D(1, 2), 1.0e-10);
-        Line l2 = new Line(new Point2D(2, 2), new Point2D(3, 3), 1.0e-10);
+        Line l1 = new Line(Point2D.of(0, 1), Point2D.of(1, 2), 1.0e-10);
+        Line l2 = new Line(Point2D.of(2, 2), Point2D.of(3, 3), 1.0e-10);
         Assert.assertTrue(l1.isParallelTo(l2));
-        Line l3 = new Line(new Point2D(1, 0), new Point2D(0.5, -0.5), 1.0e-10);
+        Line l3 = new Line(Point2D.of(1, 0), Point2D.of(0.5, -0.5), 1.0e-10);
         Assert.assertTrue(l1.isParallelTo(l3));
-        Line l4 = new Line(new Point2D(1, 0), new Point2D(0.5, -0.51), 1.0e-10);
+        Line l4 = new Line(Point2D.of(1, 0), Point2D.of(0.5, -0.51), 1.0e-10);
         Assert.assertTrue(! l1.isParallelTo(l4));
     }
 
     @Test
     public void testTransform() {
 
-        Line l1 = new Line(new Point2D(1.0 ,1.0), new Point2D(4.0 ,1.0), 1.0e-10);
+        Line l1 = new Line(Point2D.of(1.0 ,1.0), Point2D.of(4.0 ,1.0), 1.0e-10);
         Transform<Point2D, Point1D> t1 =
             Line.getTransform(0.0, 0.5, -1.0, 0.0, 1.0, 1.5);
         Assert.assertEquals(0.5 * Math.PI,
                             ((Line) t1.apply(l1)).getAngle(),
                             1.0e-10);
 
-        Line l2 = new Line(new Point2D(0.0, 0.0), new Point2D(1.0, 1.0), 1.0e-10);
+        Line l2 = new Line(Point2D.of(0.0, 0.0), Point2D.of(1.0, 1.0), 1.0e-10);
         Transform<Point2D, Point1D> t2 =
             Line.getTransform(0.0, 0.5, -1.0, 0.0, 1.0, 1.5);
         Assert.assertEquals(Math.atan2(1.0, -2.0),
@@ -119,8 +119,8 @@ public void testTransform() {
 
     @Test
     public void testIntersection() {
-        Line    l1 = new Line(new Point2D( 0, 1), new Point2D(1, 2), 1.0e-10);
-        Line    l2 = new Line(new Point2D(-1, 2), new Point2D(2, 1), 1.0e-10);
+        Line    l1 = new Line(Point2D.of( 0, 1), Point2D.of(1, 2), 1.0e-10);
+        Line    l2 = new Line(Point2D.of(-1, 2), Point2D.of(2, 1), 1.0e-10);
         Point2D p  = l1.intersection(l2);
         Assert.assertEquals(0.5, p.getX(), 1.0e-10);
         Assert.assertEquals(1.5, p.getY(), 1.0e-10);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/NestedLoopsTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/NestedLoopsTest.java
index 26cec45..8521c9f 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/NestedLoopsTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/NestedLoopsTest.java
@@ -31,11 +31,11 @@
     @SuppressWarnings("unchecked")
     @Test
     public void testNestedLoops() throws Exception {
-        Point2D oneOne = new Point2D(1.0, 1.0);
-        Point2D oneNegativeOne = new Point2D(1.0, -1.0);
-        Point2D negativeOneNegativeOne = new Point2D(-1.0, -1.0);
-        Point2D negativeOneOne = new Point2D(-1.0, 1.0);
-        Point2D origin = new Point2D(0, 0);
+        Point2D oneOne = Point2D.of(1.0, 1.0);
+        Point2D oneNegativeOne = Point2D.of(1.0, -1.0);
+        Point2D negativeOneNegativeOne = Point2D.of(-1.0, -1.0);
+        Point2D negativeOneOne = Point2D.of(-1.0, 1.0);
+        Point2D origin = Point2D.of(0, 0);
 
         Point2D [] vertices = new Point2D[]{
                 oneOne,
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
index 6892198..e6cf422 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
@@ -156,7 +156,7 @@ public void testEquals() {
     public void testToString() {
         // arrange
         Point2D p = Point2D.of(1, 2);
-        Pattern pattern = Pattern.compile("\\(1.{0,2}; 2.{0,2}\\)");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
 
         // act
         String str = p.toString();
@@ -166,6 +166,25 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point2D.parse("(1, 2)"), 1, 2);
+        checkPoint(Point2D.parse("(-1, -2)"), -1, -2);
+
+        checkPoint(Point2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3);
+
+        checkPoint(Point2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY);
+
+        checkPoint(Point2D.parse(Point2D.ZERO.toString()), 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point2D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolygonsSetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolygonsSetTest.java
index f565ea6..55eaa86 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolygonsSetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolygonsSetTest.java
@@ -55,13 +55,13 @@ public void testFull() {
         EuclideanTestUtils.assertCoordinatesEqual(Point2D.NaN, poly.getBarycenter(), TEST_TOLERANCE);
 
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY),
+                Point2D.of(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY),
                 Point2D.ZERO,
-                new Point2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
+                Point2D.of(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
 
         for (double y = -1; y < 1; y += 0.1) {
             for (double x = -1; x < 1; x += 0.1) {
-                EuclideanTestUtils.assertNegativeInfinity(poly.projectToBoundary(new Point2D(x, y)).getOffset());
+                EuclideanTestUtils.assertNegativeInfinity(poly.projectToBoundary(Point2D.of(x, y)).getOffset());
             }
         }
     }
@@ -81,14 +81,14 @@ public void testEmpty() {
         EuclideanTestUtils.assertCoordinatesEqual(Point2D.NaN, poly.getBarycenter(), TEST_TOLERANCE);
 
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY),
+                Point2D.of(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY),
                 Point2D.ZERO,
-                new Point2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
+                Point2D.of(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
 
 
         for (double y = -1; y < 1; y += 0.1) {
             for (double x = -1; x < 1; x += 0.1) {
-                EuclideanTestUtils.assertPositiveInfinity(poly.projectToBoundary(new Point2D(x, y)).getOffset());
+                EuclideanTestUtils.assertPositiveInfinity(poly.projectToBoundary(Point2D.of(x, y)).getOffset());
             }
         }
     }
@@ -96,7 +96,7 @@ public void testEmpty() {
     @Test
     public void testInfiniteLines_single() {
         // arrange
-        Line line = new Line(new Point2D(0, 0), new Point2D(1, 1), TEST_TOLERANCE);
+        Line line = new Line(Point2D.of(0, 0), Point2D.of(1, 1), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
         boundaries.add(line.wholeHyperplane());
@@ -115,25 +115,25 @@ public void testInfiniteLines_single() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                line.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line.toSpace(new Point1D(Float.MAX_VALUE))
+                line.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line.toSpace(Point1D.of(Float.MAX_VALUE))
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(1, -1),
-                new Point2D(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY));
+                Point2D.of(1, -1),
+                Point2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY));
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(-1, 1),
-                new Point2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+                Point2D.of(-1, 1),
+                Point2D.of(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
         checkPoints(Region.Location.BOUNDARY, poly, Point2D.ZERO);
     }
 
     @Test
     public void testInfiniteLines_twoIntersecting() {
         // arrange
-        Line line1 = new Line(new Point2D(0, 0), new Point2D(1, 1), TEST_TOLERANCE);
-        Line line2 = new Line(new Point2D(1, -1), new Point2D(0, 0), TEST_TOLERANCE);
+        Line line1 = new Line(Point2D.of(0, 0), Point2D.of(1, 1), TEST_TOLERANCE);
+        Line line2 = new Line(Point2D.of(1, -1), Point2D.of(0, 0), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
         boundaries.add(line1.wholeHyperplane());
@@ -153,25 +153,25 @@ public void testInfiniteLines_twoIntersecting() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                line2.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line2.toSpace(new Point1D(Float.MAX_VALUE))
+                line2.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line2.toSpace(Point1D.of(Float.MAX_VALUE))
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(-1, 0),
-                new Point2D(-Float.MAX_VALUE, Float.MAX_VALUE / 2.0));
+                Point2D.of(-1, 0),
+                Point2D.of(-Float.MAX_VALUE, Float.MAX_VALUE / 2.0));
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(1, 0),
-                new Point2D(Float.MAX_VALUE, Float.MAX_VALUE / 2.0));
+                Point2D.of(1, 0),
+                Point2D.of(Float.MAX_VALUE, Float.MAX_VALUE / 2.0));
         checkPoints(Region.Location.BOUNDARY, poly, Point2D.ZERO);
     }
 
     @Test
     public void testInfiniteLines_twoParallel_facingIn() {
         // arrange
-        Line line1 = new Line(new Point2D(1, 1), new Point2D(0, 1), TEST_TOLERANCE);
-        Line line2 = new Line(new Point2D(0, -1), new Point2D(1, -1), TEST_TOLERANCE);
+        Line line1 = new Line(Point2D.of(1, 1), Point2D.of(0, 1), TEST_TOLERANCE);
+        Line line2 = new Line(Point2D.of(0, -1), Point2D.of(1, -1), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
         boundaries.add(line1.wholeHyperplane());
@@ -191,33 +191,33 @@ public void testInfiniteLines_twoParallel_facingIn() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                line1.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line1.toSpace(new Point1D(Float.MAX_VALUE))
+                line1.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line1.toSpace(Point1D.of(Float.MAX_VALUE))
             },
             {
                 null,
-                line2.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line2.toSpace(new Point1D(Float.MAX_VALUE))
+                line2.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line2.toSpace(Point1D.of(Float.MAX_VALUE))
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(0, 0),
-                new Point2D(0, 0.9),
-                new Point2D(0, -0.9));
+                Point2D.of(0, 0),
+                Point2D.of(0, 0.9),
+                Point2D.of(0, -0.9));
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(0, 1.1),
-                new Point2D(0, -1.1));
+                Point2D.of(0, 1.1),
+                Point2D.of(0, -1.1));
         checkPoints(Region.Location.BOUNDARY, poly,
-                new Point2D(0, 1),
-                new Point2D(0, -1));
+                Point2D.of(0, 1),
+                Point2D.of(0, -1));
     }
 
     @Test
     public void testInfiniteLines_twoParallel_facingOut() {
         // arrange
-        Line line1 = new Line(new Point2D(0, 1), new Point2D(1, 1), TEST_TOLERANCE);
-        Line line2 = new Line(new Point2D(1, -1), new Point2D(0, -1), TEST_TOLERANCE);
+        Line line1 = new Line(Point2D.of(0, 1), Point2D.of(1, 1), TEST_TOLERANCE);
+        Line line2 = new Line(Point2D.of(1, -1), Point2D.of(0, -1), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
         boundaries.add(line1.wholeHyperplane());
@@ -237,38 +237,38 @@ public void testInfiniteLines_twoParallel_facingOut() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                line1.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line1.toSpace(new Point1D(Float.MAX_VALUE))
+                line1.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line1.toSpace(Point1D.of(Float.MAX_VALUE))
             },
             {
                 null,
-                line2.toSpace(new Point1D(-Float.MAX_VALUE)),
-                line2.toSpace(new Point1D(Float.MAX_VALUE))
+                line2.toSpace(Point1D.of(-Float.MAX_VALUE)),
+                line2.toSpace(Point1D.of(Float.MAX_VALUE))
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(0, 0),
-                new Point2D(0, 0.9),
-                new Point2D(0, -0.9));
+                Point2D.of(0, 0),
+                Point2D.of(0, 0.9),
+                Point2D.of(0, -0.9));
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(0, 1.1),
-                new Point2D(0, -1.1));
+                Point2D.of(0, 1.1),
+                Point2D.of(0, -1.1));
         checkPoints(Region.Location.BOUNDARY, poly,
-                new Point2D(0, 1),
-                new Point2D(0, -1));
+                Point2D.of(0, 1),
+                Point2D.of(0, -1));
     }
 
     @Test
     public void testMixedFiniteAndInfiniteLines_explicitInfiniteBoundaries() {
         // arrange
-        Line line1 = new Line(new Point2D(3, 3), new Point2D(0, 3), TEST_TOLERANCE);
-        Line line2 = new Line(new Point2D(0, -3), new Point2D(3, -3), TEST_TOLERANCE);
+        Line line1 = new Line(Point2D.of(3, 3), Point2D.of(0, 3), TEST_TOLERANCE);
+        Line line2 = new Line(Point2D.of(0, -3), Point2D.of(3, -3), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
         boundaries.add(line1.wholeHyperplane());
         boundaries.add(line2.wholeHyperplane());
-        boundaries.add(buildSegment(new Point2D(0, 3), new Point2D(0, -3)));
+        boundaries.add(buildSegment(Point2D.of(0, 3), Point2D.of(0, -3)));
 
         // act
         PolygonsSet poly = new PolygonsSet(boundaries, TEST_TOLERANCE);
@@ -284,25 +284,25 @@ public void testMixedFiniteAndInfiniteLines_explicitInfiniteBoundaries() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                new Point2D(1, 3), // dummy point
-                new Point2D(0, 3),
-                new Point2D(0, -3),
-                new Point2D(1, -3) // dummy point
+                Point2D.of(1, 3), // dummy point
+                Point2D.of(0, 3),
+                Point2D.of(0, -3),
+                Point2D.of(1, -3) // dummy point
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(0.1, 2.9),
-                new Point2D(0.1, 0),
-                new Point2D(0.1, -2.9));
+                Point2D.of(0.1, 2.9),
+                Point2D.of(0.1, 0),
+                Point2D.of(0.1, -2.9));
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(0, 3.1),
-                new Point2D(-0.5, 0),
-                new Point2D(0, -3.1));
+                Point2D.of(0, 3.1),
+                Point2D.of(-0.5, 0),
+                Point2D.of(0, -3.1));
         checkPoints(Region.Location.BOUNDARY, poly,
-                new Point2D(3, 3),
-                new Point2D(0, 0),
-                new Point2D(3, -3));
+                Point2D.of(3, 3),
+                Point2D.of(0, 0),
+                Point2D.of(3, -3));
     }
 
     // The polygon in this test is created from finite boundaries but the generated
@@ -313,11 +313,11 @@ public void testMixedFiniteAndInfiniteLines_explicitInfiniteBoundaries() {
     @Test
     public void testMixedFiniteAndInfiniteLines_impliedInfiniteBoundaries() {
         // arrange
-        Line line = new Line(new Point2D(3, 0), new Point2D(3, 3), TEST_TOLERANCE);
+        Line line = new Line(Point2D.of(3, 0), Point2D.of(3, 3), TEST_TOLERANCE);
 
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
-        boundaries.add(buildSegment(new Point2D(0, 3), new Point2D(0, 0)));
-        boundaries.add(buildSegment(new Point2D(0, 0), new Point2D(3, 0)));
+        boundaries.add(buildSegment(Point2D.of(0, 3), Point2D.of(0, 0)));
+        boundaries.add(buildSegment(Point2D.of(0, 0), Point2D.of(3, 0)));
         boundaries.add(new SubLine(line, new IntervalsSet(0, Double.POSITIVE_INFINITY, TEST_TOLERANCE)));
 
         // act
@@ -334,29 +334,29 @@ public void testMixedFiniteAndInfiniteLines_impliedInfiniteBoundaries() {
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
                 null,
-                new Point2D(0, 1), // dummy point
-                new Point2D(0, 0),
-                new Point2D(3, 0),
-                new Point2D(3, 1) // dummy point
+                Point2D.of(0, 1), // dummy point
+                Point2D.of(0, 0),
+                Point2D.of(3, 0),
+                Point2D.of(3, 1) // dummy point
             }
         }, poly.getVertices());
 
         checkPoints(Region.Location.INSIDE, poly,
-                new Point2D(0.1, Float.MAX_VALUE),
-                new Point2D(0.1, 0.1),
-                new Point2D(1.5, 0.1),
-                new Point2D(2.9, 0.1),
-                new Point2D(2.9, Float.MAX_VALUE));
+                Point2D.of(0.1, Float.MAX_VALUE),
+                Point2D.of(0.1, 0.1),
+                Point2D.of(1.5, 0.1),
+                Point2D.of(2.9, 0.1),
+                Point2D.of(2.9, Float.MAX_VALUE));
         checkPoints(Region.Location.OUTSIDE, poly,
-                new Point2D(-0.1, Float.MAX_VALUE),
-                new Point2D(-0.1, 0.1),
-                new Point2D(1.5, -0.1),
-                new Point2D(3.1, 0.1),
-                new Point2D(3.1, Float.MAX_VALUE));
+                Point2D.of(-0.1, Float.MAX_VALUE),
+                Point2D.of(-0.1, 0.1),
+                Point2D.of(1.5, -0.1),
+                Point2D.of(3.1, 0.1),
+                Point2D.of(3.1, Float.MAX_VALUE));
         checkPoints(Region.Location.BOUNDARY, poly,
-                new Point2D(0, 1),
-                new Point2D(1, 0),
-                new Point2D(3, 1));
+                Point2D.of(0, 1),
+                Point2D.of(1, 0),
+                Point2D.of(3, 1));
     }
 
     @Test
@@ -369,42 +369,42 @@ public void testBox() {
         Assert.assertEquals(8.0, box.getBoundarySize(), TEST_TOLERANCE);
         Assert.assertFalse(box.isEmpty());
         Assert.assertFalse(box.isFull());
-        EuclideanTestUtils.assertCoordinatesEqual(new Point2D(1, 0), box.getBarycenter(), TEST_TOLERANCE);
+        EuclideanTestUtils.assertCoordinatesEqual(Point2D.of(1, 0), box.getBarycenter(), TEST_TOLERANCE);
 
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
-                new Point2D(2, -1),
-                new Point2D(2, 1),
-                new Point2D(0, 1),
-                new Point2D(0, -1)
+                Point2D.of(2, -1),
+                Point2D.of(2, 1),
+                Point2D.of(0, 1),
+                Point2D.of(0, -1)
             }
         }, box.getVertices());
 
         checkPoints(Region.Location.INSIDE, box,
-                new Point2D(0.1, 0),
-                new Point2D(1.9, 0),
-                new Point2D(1, 0.9),
-                new Point2D(1, -0.9));
+                Point2D.of(0.1, 0),
+                Point2D.of(1.9, 0),
+                Point2D.of(1, 0.9),
+                Point2D.of(1, -0.9));
         checkPoints(Region.Location.OUTSIDE, box,
-                new Point2D(-0.1, 0),
-                new Point2D(2.1, 0),
-                new Point2D(1, -1.1),
-                new Point2D(1, 1.1));
+                Point2D.of(-0.1, 0),
+                Point2D.of(2.1, 0),
+                Point2D.of(1, -1.1),
+                Point2D.of(1, 1.1));
         checkPoints(Region.Location.BOUNDARY, box,
-                new Point2D(0, 0),
-                new Point2D(2, 0),
-                new Point2D(1, 1),
-                new Point2D(1, -1));
+                Point2D.of(0, 0),
+                Point2D.of(2, 0),
+                Point2D.of(1, 1),
+                Point2D.of(1, -1));
     }
 
     @Test
     public void testInvertedBox() {
         // arrange
         List<SubHyperplane<Point2D>> boundaries = new ArrayList<SubHyperplane<Point2D>>();
-        boundaries.add(buildSegment(new Point2D(0, -1), new Point2D(0, 1)));
-        boundaries.add(buildSegment(new Point2D(2, 1), new Point2D(2, -1)));
-        boundaries.add(buildSegment(new Point2D(0, 1), new Point2D(2, 1)));
-        boundaries.add(buildSegment(new Point2D(2, -1), new Point2D(0, -1)));
+        boundaries.add(buildSegment(Point2D.of(0, -1), Point2D.of(0, 1)));
+        boundaries.add(buildSegment(Point2D.of(2, 1), Point2D.of(2, -1)));
+        boundaries.add(buildSegment(Point2D.of(0, 1), Point2D.of(2, 1)));
+        boundaries.add(buildSegment(Point2D.of(2, -1), Point2D.of(0, -1)));
 
         // act
         PolygonsSet box = new PolygonsSet(boundaries, TEST_TOLERANCE);
@@ -418,28 +418,28 @@ public void testInvertedBox() {
 
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
-                new Point2D(0, -1),
-                new Point2D(0, 1),
-                new Point2D(2, 1),
-                new Point2D(2, -1)
+                Point2D.of(0, -1),
+                Point2D.of(0, 1),
+                Point2D.of(2, 1),
+                Point2D.of(2, -1)
             }
         }, box.getVertices());
 
         checkPoints(Region.Location.OUTSIDE, box,
-                new Point2D(0.1, 0),
-                new Point2D(1.9, 0),
-                new Point2D(1, 0.9),
-                new Point2D(1, -0.9));
+                Point2D.of(0.1, 0),
+                Point2D.of(1.9, 0),
+                Point2D.of(1, 0.9),
+                Point2D.of(1, -0.9));
         checkPoints(Region.Location.INSIDE, box,
-                new Point2D(-0.1, 0),
-                new Point2D(2.1, 0),
-                new Point2D(1, -1.1),
-                new Point2D(1, 1.1));
+                Point2D.of(-0.1, 0),
+                Point2D.of(2.1, 0),
+                Point2D.of(1, -1.1),
+                Point2D.of(1, 1.1));
         checkPoints(Region.Location.BOUNDARY, box,
-                new Point2D(0, 0),
-                new Point2D(2, 0),
-                new Point2D(1, 1),
-                new Point2D(1, -1));
+                Point2D.of(0, 0),
+                Point2D.of(2, 0),
+                Point2D.of(1, 1),
+                Point2D.of(1, -1));
     }
 
     @Test
@@ -447,16 +447,16 @@ public void testSimplyConnected() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D(36.0, 22.0),
-                new Point2D(39.0, 32.0),
-                new Point2D(19.0, 32.0),
-                new Point2D( 6.0, 16.0),
-                new Point2D(31.0, 10.0),
-                new Point2D(42.0, 16.0),
-                new Point2D(34.0, 20.0),
-                new Point2D(29.0, 19.0),
-                new Point2D(23.0, 22.0),
-                new Point2D(33.0, 25.0)
+                Point2D.of(36.0, 22.0),
+                Point2D.of(39.0, 32.0),
+                Point2D.of(19.0, 32.0),
+                Point2D.of( 6.0, 16.0),
+                Point2D.of(31.0, 10.0),
+                Point2D.of(42.0, 16.0),
+                Point2D.of(34.0, 20.0),
+                Point2D.of(29.0, 19.0),
+                Point2D.of(23.0, 22.0),
+                Point2D.of(33.0, 25.0)
             }
         };
 
@@ -465,22 +465,22 @@ public void testSimplyConnected() {
 
         // assert
         checkPoints(Region.Location.INSIDE, set,
-            new Point2D(30.0, 15.0),
-            new Point2D(15.0, 20.0),
-            new Point2D(24.0, 25.0),
-            new Point2D(35.0, 30.0),
-            new Point2D(19.0, 17.0));
+            Point2D.of(30.0, 15.0),
+            Point2D.of(15.0, 20.0),
+            Point2D.of(24.0, 25.0),
+            Point2D.of(35.0, 30.0),
+            Point2D.of(19.0, 17.0));
         checkPoints(Region.Location.OUTSIDE, set,
-            new Point2D(50.0, 30.0),
-            new Point2D(30.0, 35.0),
-            new Point2D(10.0, 25.0),
-            new Point2D(10.0, 10.0),
-            new Point2D(40.0, 10.0),
-            new Point2D(50.0, 15.0),
-            new Point2D(30.0, 22.0));
+            Point2D.of(50.0, 30.0),
+            Point2D.of(30.0, 35.0),
+            Point2D.of(10.0, 25.0),
+            Point2D.of(10.0, 10.0),
+            Point2D.of(40.0, 10.0),
+            Point2D.of(50.0, 15.0),
+            Point2D.of(30.0, 22.0));
         checkPoints(Region.Location.BOUNDARY, set,
-            new Point2D(30.0, 32.0),
-            new Point2D(34.0, 20.0));
+            Point2D.of(30.0, 32.0),
+            Point2D.of(34.0, 20.0));
 
         checkVertexLoopsEquivalent(vertices, set.getVertices());
     }
@@ -490,16 +490,16 @@ public void testStair() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0, 0.0),
-                new Point2D( 0.0, 2.0),
-                new Point2D(-0.1, 2.0),
-                new Point2D(-0.1, 1.0),
-                new Point2D(-0.3, 1.0),
-                new Point2D(-0.3, 1.5),
-                new Point2D(-1.3, 1.5),
-                new Point2D(-1.3, 2.0),
-                new Point2D(-1.8, 2.0),
-                new Point2D(-1.8 - 1.0 / Math.sqrt(2.0),
+                Point2D.of( 0.0, 0.0),
+                Point2D.of( 0.0, 2.0),
+                Point2D.of(-0.1, 2.0),
+                Point2D.of(-0.1, 1.0),
+                Point2D.of(-0.3, 1.0),
+                Point2D.of(-0.3, 1.5),
+                Point2D.of(-1.3, 1.5),
+                Point2D.of(-1.3, 2.0),
+                Point2D.of(-1.8, 2.0),
+                Point2D.of(-1.8 - 1.0 / Math.sqrt(2.0),
                             2.0 - 1.0 / Math.sqrt(2.0))
             }
         };
@@ -518,15 +518,15 @@ public void testHole() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D(0.0, 0.0),
-                new Point2D(3.0, 0.0),
-                new Point2D(3.0, 3.0),
-                new Point2D(0.0, 3.0)
+                Point2D.of(0.0, 0.0),
+                Point2D.of(3.0, 0.0),
+                Point2D.of(3.0, 3.0),
+                Point2D.of(0.0, 3.0)
             }, new Point2D[] {
-                new Point2D(1.0, 2.0),
-                new Point2D(2.0, 2.0),
-                new Point2D(2.0, 1.0),
-                new Point2D(1.0, 1.0)
+                Point2D.of(1.0, 2.0),
+                Point2D.of(2.0, 2.0),
+                Point2D.of(2.0, 1.0),
+                Point2D.of(1.0, 1.0)
             }
         };
 
@@ -535,34 +535,34 @@ public void testHole() {
 
         // assert
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(0.5, 0.5),
-            new Point2D(1.5, 0.5),
-            new Point2D(2.5, 0.5),
-            new Point2D(0.5, 1.5),
-            new Point2D(2.5, 1.5),
-            new Point2D(0.5, 2.5),
-            new Point2D(1.5, 2.5),
-            new Point2D(2.5, 2.5),
-            new Point2D(0.5, 1.0)
+            Point2D.of(0.5, 0.5),
+            Point2D.of(1.5, 0.5),
+            Point2D.of(2.5, 0.5),
+            Point2D.of(0.5, 1.5),
+            Point2D.of(2.5, 1.5),
+            Point2D.of(0.5, 2.5),
+            Point2D.of(1.5, 2.5),
+            Point2D.of(2.5, 2.5),
+            Point2D.of(0.5, 1.0)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D(1.5, 1.5),
-            new Point2D(3.5, 1.0),
-            new Point2D(4.0, 1.5),
-            new Point2D(6.0, 6.0)
+            Point2D.of(1.5, 1.5),
+            Point2D.of(3.5, 1.0),
+            Point2D.of(4.0, 1.5),
+            Point2D.of(6.0, 6.0)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(1.5, 0.0),
-            new Point2D(1.5, 1.0),
-            new Point2D(1.5, 2.0),
-            new Point2D(1.5, 3.0),
-            new Point2D(3.0, 3.0)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(1.5, 0.0),
+            Point2D.of(1.5, 1.0),
+            Point2D.of(1.5, 2.0),
+            Point2D.of(1.5, 3.0),
+            Point2D.of(3.0, 3.0)
         });
         checkVertexLoopsEquivalent(vertices, set.getVertices());
 
         for (double x = -0.999; x < 3.999; x += 0.11) {
-            Point2D v = new Point2D(x, x + 0.5);
+            Point2D v = Point2D.of(x, x + 0.5);
             BoundaryProjection<Point2D> projection = set.projectToBoundary(v);
             Assert.assertTrue(projection.getOriginal() == v);
             Point2D p = projection.getProjected();
@@ -589,7 +589,7 @@ public void testHole() {
             } else {
                 Assert.assertEquals(3.0,      p.getX(), TEST_TOLERANCE);
                 Assert.assertEquals(3.0,      p.getY(), TEST_TOLERANCE);
-                Assert.assertEquals(+v.distance(new Point2D(3, 3)), projection.getOffset(), TEST_TOLERANCE);
+                Assert.assertEquals(+v.distance(Point2D.of(3, 3)), projection.getOffset(), TEST_TOLERANCE);
             }
         }
     }
@@ -599,13 +599,13 @@ public void testDisjointPolygons() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D(0.0, 1.0),
-                new Point2D(2.0, 1.0),
-                new Point2D(1.0, 2.0)
+                Point2D.of(0.0, 1.0),
+                Point2D.of(2.0, 1.0),
+                Point2D.of(1.0, 2.0)
             }, new Point2D[] {
-                new Point2D(4.0, 0.0),
-                new Point2D(5.0, 1.0),
-                new Point2D(3.0, 1.0)
+                Point2D.of(4.0, 0.0),
+                Point2D.of(5.0, 1.0),
+                Point2D.of(3.0, 1.0)
             }
         };
 
@@ -613,21 +613,21 @@ public void testDisjointPolygons() {
         PolygonsSet set = buildSet(vertices);
 
         // assert
-        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new Point2D(1.0, 1.5)));
+        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(Point2D.of(1.0, 1.5)));
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(1.0, 1.5),
-            new Point2D(4.5, 0.8)
+            Point2D.of(1.0, 1.5),
+            Point2D.of(4.5, 0.8)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D(1.0, 0.0),
-            new Point2D(3.5, 1.2),
-            new Point2D(2.5, 1.0),
-            new Point2D(3.0, 4.0)
+            Point2D.of(1.0, 0.0),
+            Point2D.of(3.5, 1.2),
+            Point2D.of(2.5, 1.0),
+            Point2D.of(3.0, 4.0)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(3.5, 0.5),
-            new Point2D(0.0, 1.0)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(3.5, 0.5),
+            Point2D.of(0.0, 1.0)
         });
         checkVertexLoopsEquivalent(vertices, set.getVertices());
     }
@@ -637,12 +637,12 @@ public void testOppositeHyperplanes() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D(1.0, 0.0),
-                new Point2D(2.0, 1.0),
-                new Point2D(3.0, 1.0),
-                new Point2D(2.0, 2.0),
-                new Point2D(1.0, 1.0),
-                new Point2D(0.0, 1.0)
+                Point2D.of(1.0, 0.0),
+                Point2D.of(2.0, 1.0),
+                Point2D.of(3.0, 1.0),
+                Point2D.of(2.0, 2.0),
+                Point2D.of(1.0, 1.0),
+                Point2D.of(0.0, 1.0)
             }
         };
 
@@ -658,14 +658,14 @@ public void testSingularPoint() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 1.0,  0.0),
-                new Point2D( 1.0,  1.0),
-                new Point2D( 0.0,  1.0),
-                new Point2D( 0.0,  0.0),
-                new Point2D(-1.0,  0.0),
-                new Point2D(-1.0, -1.0),
-                new Point2D( 0.0, -1.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 1.0,  0.0),
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 0.0,  1.0),
+                Point2D.of( 0.0,  0.0),
+                Point2D.of(-1.0,  0.0),
+                Point2D.of(-1.0, -1.0),
+                Point2D.of( 0.0, -1.0)
             }
         };
 
@@ -675,16 +675,16 @@ public void testSingularPoint() {
         // assert
         checkVertexLoopsEquivalent(new Point2D[][] {
             {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 1.0,  0.0),
-                new Point2D( 1.0,  1.0),
-                new Point2D( 0.0,  1.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 1.0,  0.0),
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 0.0,  1.0)
             },
             {
-                new Point2D( 0.0,  0.0),
-                new Point2D(-1.0,  0.0),
-                new Point2D(-1.0, -1.0),
-                new Point2D( 0.0, -1.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of(-1.0,  0.0),
+                Point2D.of(-1.0, -1.0),
+                Point2D.of( 0.0, -1.0)
             }
         }, set.getVertices());
     }
@@ -694,14 +694,14 @@ public void testLineIntersection() {
         // arrange
         Point2D[][] vertices = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0),
-                new Point2D( 1.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0),
+                Point2D.of( 1.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         };
 
@@ -709,34 +709,34 @@ public void testLineIntersection() {
         PolygonsSet set = buildSet(vertices);
 
         // assert
-        Line l1 = new Line(new Point2D(-1.5, 0.0), Math.PI / 4, TEST_TOLERANCE);
+        Line l1 = new Line(Point2D.of(-1.5, 0.0), Math.PI / 4, TEST_TOLERANCE);
         SubLine s1 = (SubLine) set.intersection(l1.wholeHyperplane());
         List<Interval> i1 = ((IntervalsSet) s1.getRemainingRegion()).asList();
         Assert.assertEquals(2, i1.size());
         Interval v10 = i1.get(0);
-        Point2D p10Lower = l1.toSpace(new Point1D(v10.getInf()));
+        Point2D p10Lower = l1.toSpace(Point1D.of(v10.getInf()));
         Assert.assertEquals(0.0, p10Lower.getX(), TEST_TOLERANCE);
         Assert.assertEquals(1.5, p10Lower.getY(), TEST_TOLERANCE);
-        Point2D p10Upper = l1.toSpace(new Point1D(v10.getSup()));
+        Point2D p10Upper = l1.toSpace(Point1D.of(v10.getSup()));
         Assert.assertEquals(0.5, p10Upper.getX(), TEST_TOLERANCE);
         Assert.assertEquals(2.0, p10Upper.getY(), TEST_TOLERANCE);
         Interval v11 = i1.get(1);
-        Point2D p11Lower = l1.toSpace(new Point1D(v11.getInf()));
+        Point2D p11Lower = l1.toSpace(Point1D.of(v11.getInf()));
         Assert.assertEquals(1.0, p11Lower.getX(), TEST_TOLERANCE);
         Assert.assertEquals(2.5, p11Lower.getY(), TEST_TOLERANCE);
-        Point2D p11Upper = l1.toSpace(new Point1D(v11.getSup()));
+        Point2D p11Upper = l1.toSpace(Point1D.of(v11.getSup()));
         Assert.assertEquals(1.5, p11Upper.getX(), TEST_TOLERANCE);
         Assert.assertEquals(3.0, p11Upper.getY(), TEST_TOLERANCE);
 
-        Line l2 = new Line(new Point2D(-1.0, 2.0), 0, TEST_TOLERANCE);
+        Line l2 = new Line(Point2D.of(-1.0, 2.0), 0, TEST_TOLERANCE);
         SubLine s2 = (SubLine) set.intersection(l2.wholeHyperplane());
         List<Interval> i2 = ((IntervalsSet) s2.getRemainingRegion()).asList();
         Assert.assertEquals(1, i2.size());
         Interval v20 = i2.get(0);
-        Point2D p20Lower = l2.toSpace(new Point1D(v20.getInf()));
+        Point2D p20Lower = l2.toSpace(Point1D.of(v20.getInf()));
         Assert.assertEquals(1.0, p20Lower.getX(), TEST_TOLERANCE);
         Assert.assertEquals(2.0, p20Lower.getY(), TEST_TOLERANCE);
-        Point2D p20Upper = l2.toSpace(new Point1D(v20.getSup()));
+        Point2D p20Upper = l2.toSpace(Point1D.of(v20.getSup()));
         Assert.assertEquals(3.0, p20Upper.getX(), TEST_TOLERANCE);
         Assert.assertEquals(2.0, p20Upper.getY(), TEST_TOLERANCE);
     }
@@ -746,18 +746,18 @@ public void testUnlimitedSubHyperplane() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D(0.0, 0.0),
-                new Point2D(4.0, 0.0),
-                new Point2D(1.4, 1.5),
-                new Point2D(0.0, 3.5)
+                Point2D.of(0.0, 0.0),
+                Point2D.of(4.0, 0.0),
+                Point2D.of(1.4, 1.5),
+                Point2D.of(0.0, 3.5)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D(1.4,  0.2),
-                new Point2D(2.8, -1.2),
-                new Point2D(2.5,  0.6)
+                Point2D.of(1.4,  0.2),
+                Point2D.of(2.8, -1.2),
+                Point2D.of(2.5,  0.6)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -772,13 +772,13 @@ public void testUnlimitedSubHyperplane() {
         checkVertexLoopsEquivalent(vertices2, set2.getVertices());
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D(0.0,  0.0),
-                new Point2D(1.6,  0.0),
-                new Point2D(2.8, -1.2),
-                new Point2D(2.6,  0.0),
-                new Point2D(4.0,  0.0),
-                new Point2D(1.4,  1.5),
-                new Point2D(0.0,  3.5)
+                Point2D.of(0.0,  0.0),
+                Point2D.of(1.6,  0.0),
+                Point2D.of(2.8, -1.2),
+                Point2D.of(2.6,  0.0),
+                Point2D.of(4.0,  0.0),
+                Point2D.of(1.4,  1.5),
+                Point2D.of(0.0,  3.5)
             }
         }, set.getVertices());
     }
@@ -788,19 +788,19 @@ public void testUnion() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -814,41 +814,41 @@ public void testUnion() {
         checkVertexLoopsEquivalent(vertices2, set2.getVertices());
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0),
-                new Point2D( 1.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0),
+                Point2D.of( 1.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         }, set.getVertices());
 
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(0.5, 0.5),
-            new Point2D(2.0, 2.0),
-            new Point2D(2.5, 2.5),
-            new Point2D(0.5, 1.5),
-            new Point2D(1.5, 1.5),
-            new Point2D(1.5, 0.5),
-            new Point2D(1.5, 2.5),
-            new Point2D(2.5, 1.5),
-            new Point2D(2.5, 2.5)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(0.5, 0.5),
+            Point2D.of(2.0, 2.0),
+            Point2D.of(2.5, 2.5),
+            Point2D.of(0.5, 1.5),
+            Point2D.of(1.5, 1.5),
+            Point2D.of(1.5, 0.5),
+            Point2D.of(1.5, 2.5),
+            Point2D.of(2.5, 1.5),
+            Point2D.of(2.5, 2.5)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D(-0.5, 0.5),
-            new Point2D( 0.5, 2.5),
-            new Point2D( 2.5, 0.5),
-            new Point2D( 3.5, 2.5)
+            Point2D.of(-0.5, 0.5),
+            Point2D.of( 0.5, 2.5),
+            Point2D.of( 2.5, 0.5),
+            Point2D.of( 3.5, 2.5)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(0.0, 0.0),
-            new Point2D(0.5, 2.0),
-            new Point2D(2.0, 0.5),
-            new Point2D(2.5, 1.0),
-            new Point2D(3.0, 2.5)
+            Point2D.of(0.0, 0.0),
+            Point2D.of(0.5, 2.0),
+            Point2D.of(2.0, 0.5),
+            Point2D.of(2.5, 1.0),
+            Point2D.of(3.0, 2.5)
         });
     }
 
@@ -857,19 +857,19 @@ public void testIntersection() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -883,27 +883,27 @@ public void testIntersection() {
         checkVertexLoopsEquivalent(vertices2, set2.getVertices());
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 2.0,  1.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 1.0,  2.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 2.0,  1.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 1.0,  2.0)
             }
         }, set.getVertices());
 
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(1.5, 1.5)
+            Point2D.of(1.5, 1.5)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D(0.5, 1.5),
-            new Point2D(2.5, 1.5),
-            new Point2D(1.5, 0.5),
-            new Point2D(0.5, 0.5)
+            Point2D.of(0.5, 1.5),
+            Point2D.of(2.5, 1.5),
+            Point2D.of(1.5, 0.5),
+            Point2D.of(0.5, 0.5)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(2.0, 2.0),
-            new Point2D(1.0, 1.5),
-            new Point2D(1.5, 2.0)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(2.0, 2.0),
+            Point2D.of(1.0, 1.5),
+            Point2D.of(1.5, 2.0)
         });
     }
 
@@ -912,19 +912,19 @@ public void testXor() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -938,49 +938,49 @@ public void testXor() {
         checkVertexLoopsEquivalent(vertices2, set2.getVertices());
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0),
-                new Point2D( 1.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0),
+                Point2D.of( 1.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             },
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 1.0,  2.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 2.0,  1.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 1.0,  2.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 2.0,  1.0)
             }
         }, set.getVertices());
 
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(0.5, 0.5),
-            new Point2D(2.5, 2.5),
-            new Point2D(0.5, 1.5),
-            new Point2D(1.5, 0.5),
-            new Point2D(1.5, 2.5),
-            new Point2D(2.5, 1.5),
-            new Point2D(2.5, 2.5)
+            Point2D.of(0.5, 0.5),
+            Point2D.of(2.5, 2.5),
+            Point2D.of(0.5, 1.5),
+            Point2D.of(1.5, 0.5),
+            Point2D.of(1.5, 2.5),
+            Point2D.of(2.5, 1.5),
+            Point2D.of(2.5, 2.5)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D(-0.5, 0.5),
-            new Point2D( 0.5, 2.5),
-            new Point2D( 2.5, 0.5),
-            new Point2D( 1.5, 1.5),
-            new Point2D( 3.5, 2.5)
+            Point2D.of(-0.5, 0.5),
+            Point2D.of( 0.5, 2.5),
+            Point2D.of( 2.5, 0.5),
+            Point2D.of( 1.5, 1.5),
+            Point2D.of( 3.5, 2.5)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(2.0, 2.0),
-            new Point2D(1.5, 1.0),
-            new Point2D(2.0, 1.5),
-            new Point2D(0.0, 0.0),
-            new Point2D(0.5, 2.0),
-            new Point2D(2.0, 0.5),
-            new Point2D(2.5, 1.0),
-            new Point2D(3.0, 2.5)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(2.0, 2.0),
+            Point2D.of(1.5, 1.0),
+            Point2D.of(2.0, 1.5),
+            Point2D.of(0.0, 0.0),
+            Point2D.of(0.5, 2.0),
+            Point2D.of(2.0, 0.5),
+            Point2D.of(2.5, 1.0),
+            Point2D.of(3.0, 2.5)
         });
     }
 
@@ -989,19 +989,19 @@ public void testDifference() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0,  1.0),
-                new Point2D( 3.0,  1.0),
-                new Point2D( 3.0,  3.0),
-                new Point2D( 1.0,  3.0)
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 3.0,  1.0),
+                Point2D.of( 3.0,  3.0),
+                Point2D.of( 1.0,  3.0)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -1015,41 +1015,41 @@ public void testDifference() {
         checkVertexLoopsEquivalent(vertices2, set2.getVertices());
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.0,  0.0),
-                new Point2D( 2.0,  0.0),
-                new Point2D( 2.0,  1.0),
-                new Point2D( 1.0,  1.0),
-                new Point2D( 1.0,  2.0),
-                new Point2D( 0.0,  2.0)
+                Point2D.of( 0.0,  0.0),
+                Point2D.of( 2.0,  0.0),
+                Point2D.of( 2.0,  1.0),
+                Point2D.of( 1.0,  1.0),
+                Point2D.of( 1.0,  2.0),
+                Point2D.of( 0.0,  2.0)
             }
         }, set.getVertices());
 
         checkPoints(Region.Location.INSIDE, set, new Point2D[] {
-            new Point2D(0.5, 0.5),
-            new Point2D(0.5, 1.5),
-            new Point2D(1.5, 0.5)
+            Point2D.of(0.5, 0.5),
+            Point2D.of(0.5, 1.5),
+            Point2D.of(1.5, 0.5)
         });
         checkPoints(Region.Location.OUTSIDE, set, new Point2D[] {
-            new Point2D( 2.5, 2.5),
-            new Point2D(-0.5, 0.5),
-            new Point2D( 0.5, 2.5),
-            new Point2D( 2.5, 0.5),
-            new Point2D( 1.5, 1.5),
-            new Point2D( 3.5, 2.5),
-            new Point2D( 1.5, 2.5),
-            new Point2D( 2.5, 1.5),
-            new Point2D( 2.0, 1.5),
-            new Point2D( 2.0, 2.0),
-            new Point2D( 2.5, 1.0),
-            new Point2D( 2.5, 2.5),
-            new Point2D( 3.0, 2.5)
+            Point2D.of( 2.5, 2.5),
+            Point2D.of(-0.5, 0.5),
+            Point2D.of( 0.5, 2.5),
+            Point2D.of( 2.5, 0.5),
+            Point2D.of( 1.5, 1.5),
+            Point2D.of( 3.5, 2.5),
+            Point2D.of( 1.5, 2.5),
+            Point2D.of( 2.5, 1.5),
+            Point2D.of( 2.0, 1.5),
+            Point2D.of( 2.0, 2.0),
+            Point2D.of( 2.5, 1.0),
+            Point2D.of( 2.5, 2.5),
+            Point2D.of( 3.0, 2.5)
         });
         checkPoints(Region.Location.BOUNDARY, set, new Point2D[] {
-            new Point2D(1.0, 1.0),
-            new Point2D(1.5, 1.0),
-            new Point2D(0.0, 0.0),
-            new Point2D(0.5, 2.0),
-            new Point2D(2.0, 0.5)
+            Point2D.of(1.0, 1.0),
+            Point2D.of(1.5, 1.0),
+            Point2D.of(0.0, 0.0),
+            Point2D.of(0.5, 2.0),
+            Point2D.of(2.0, 0.5)
         });
     }
 
@@ -1058,19 +1058,19 @@ public void testEmptyDifference() {
         // arrange
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.5, 3.5),
-                new Point2D( 0.5, 4.5),
-                new Point2D(-0.5, 4.5),
-                new Point2D(-0.5, 3.5)
+                Point2D.of( 0.5, 3.5),
+                Point2D.of( 0.5, 4.5),
+                Point2D.of(-0.5, 4.5),
+                Point2D.of(-0.5, 3.5)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 1.0, 2.0),
-                new Point2D( 1.0, 8.0),
-                new Point2D(-1.0, 8.0),
-                new Point2D(-1.0, 2.0)
+                Point2D.of( 1.0, 2.0),
+                Point2D.of( 1.0, 8.0),
+                Point2D.of(-1.0, 8.0),
+                Point2D.of(-1.0, 2.0)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -1089,13 +1089,13 @@ public void testChoppedHexagon() {
         double pi6   = Math.PI / 6.0;
         double sqrt3 = Math.sqrt(3.0);
         SubLine[] hyp = {
-            new Line(new Point2D(   0.0, 1.0),  5 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(-sqrt3, 1.0),  7 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(-sqrt3, 1.0),  9 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(-sqrt3, 0.0), 11 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(   0.0, 0.0), 13 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(   0.0, 1.0),  3 * pi6, TEST_TOLERANCE).wholeHyperplane(),
-            new Line(new Point2D(-5.0 * sqrt3 / 6.0, 0.0), 9 * pi6, TEST_TOLERANCE).wholeHyperplane()
+            new Line(Point2D.of(   0.0, 1.0),  5 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(-sqrt3, 1.0),  7 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(-sqrt3, 1.0),  9 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(-sqrt3, 0.0), 11 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(   0.0, 0.0), 13 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(   0.0, 1.0),  3 * pi6, TEST_TOLERANCE).wholeHyperplane(),
+            new Line(Point2D.of(-5.0 * sqrt3 / 6.0, 0.0), 9 * pi6, TEST_TOLERANCE).wholeHyperplane()
         };
         hyp[1] = (SubLine) hyp[1].split(hyp[0].getHyperplane()).getMinus();
         hyp[2] = (SubLine) hyp[2].split(hyp[1].getHyperplane()).getMinus();
@@ -1109,7 +1109,7 @@ public void testChoppedHexagon() {
         }
         PolygonsSet set = new PolygonsSet(tree, TEST_TOLERANCE);
         SubLine splitter =
-            new Line(new Point2D(-2.0 * sqrt3 / 3.0, 0.0), 9 * pi6, TEST_TOLERANCE).wholeHyperplane();
+            new Line(Point2D.of(-2.0 * sqrt3 / 3.0, 0.0), 9 * pi6, TEST_TOLERANCE).wholeHyperplane();
 
         // act
         PolygonsSet slice =
@@ -1120,7 +1120,7 @@ public void testChoppedHexagon() {
 
         // assert
         Assert.assertEquals(Region.Location.OUTSIDE,
-                            slice.checkPoint(new Point2D(0.1, 0.5)));
+                            slice.checkPoint(Point2D.of(0.1, 0.5)));
         Assert.assertEquals(11.0 / 3.0, slice.getBoundarySize(), TEST_TOLERANCE);
     }
 
@@ -1130,25 +1130,25 @@ public void testConcentric() {
         double h = Math.sqrt(3.0) / 2.0;
         Point2D[][] vertices1 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.00, 0.1 * h),
-                new Point2D( 0.05, 0.1 * h),
-                new Point2D( 0.10, 0.2 * h),
-                new Point2D( 0.05, 0.3 * h),
-                new Point2D(-0.05, 0.3 * h),
-                new Point2D(-0.10, 0.2 * h),
-                new Point2D(-0.05, 0.1 * h)
+                Point2D.of( 0.00, 0.1 * h),
+                Point2D.of( 0.05, 0.1 * h),
+                Point2D.of( 0.10, 0.2 * h),
+                Point2D.of( 0.05, 0.3 * h),
+                Point2D.of(-0.05, 0.3 * h),
+                Point2D.of(-0.10, 0.2 * h),
+                Point2D.of(-0.05, 0.1 * h)
             }
         };
         PolygonsSet set1 = buildSet(vertices1);
         Point2D[][] vertices2 = new Point2D[][] {
             new Point2D[] {
-                new Point2D( 0.00, 0.0 * h),
-                new Point2D( 0.10, 0.0 * h),
-                new Point2D( 0.20, 0.2 * h),
-                new Point2D( 0.10, 0.4 * h),
-                new Point2D(-0.10, 0.4 * h),
-                new Point2D(-0.20, 0.2 * h),
-                new Point2D(-0.10, 0.0 * h)
+                Point2D.of( 0.00, 0.0 * h),
+                Point2D.of( 0.10, 0.0 * h),
+                Point2D.of( 0.20, 0.2 * h),
+                Point2D.of( 0.10, 0.4 * h),
+                Point2D.of(-0.10, 0.4 * h),
+                Point2D.of(-0.20, 0.2 * h),
+                Point2D.of(-0.10, 0.0 * h)
             }
         };
         PolygonsSet set2 = buildSet(vertices2);
@@ -1161,86 +1161,86 @@ public void testConcentric() {
     public void testBug20040520() {
         // arrange
         BSPTree<Point2D> a0 =
-            new BSPTree<>(buildSegment(new Point2D(0.85, -0.05),
-                                                  new Point2D(0.90, -0.10)),
+            new BSPTree<>(buildSegment(Point2D.of(0.85, -0.05),
+                                                  Point2D.of(0.90, -0.10)),
                                                   new BSPTree<Point2D>(Boolean.FALSE),
                                                   new BSPTree<Point2D>(Boolean.TRUE),
                                                   null);
         BSPTree<Point2D> a1 =
-            new BSPTree<>(buildSegment(new Point2D(0.85, -0.10),
-                                                  new Point2D(0.90, -0.10)),
+            new BSPTree<>(buildSegment(Point2D.of(0.85, -0.10),
+                                                  Point2D.of(0.90, -0.10)),
                                                   new BSPTree<Point2D>(Boolean.FALSE), a0, null);
         BSPTree<Point2D> a2 =
-            new BSPTree<>(buildSegment(new Point2D(0.90, -0.05),
-                                                  new Point2D(0.85, -0.05)),
+            new BSPTree<>(buildSegment(Point2D.of(0.90, -0.05),
+                                                  Point2D.of(0.85, -0.05)),
                                                   new BSPTree<Point2D>(Boolean.FALSE), a1, null);
         BSPTree<Point2D> a3 =
-            new BSPTree<>(buildSegment(new Point2D(0.82, -0.05),
-                                                  new Point2D(0.82, -0.08)),
+            new BSPTree<>(buildSegment(Point2D.of(0.82, -0.05),
+                                                  Point2D.of(0.82, -0.08)),
                                                   new BSPTree<Point2D>(Boolean.FALSE),
                                                   new BSPTree<Point2D>(Boolean.TRUE),
                                                   null);
         BSPTree<Point2D> a4 =
-            new BSPTree<>(buildHalfLine(new Point2D(0.85, -0.05),
-                                                   new Point2D(0.80, -0.05),
+            new BSPTree<>(buildHalfLine(Point2D.of(0.85, -0.05),
+                                                   Point2D.of(0.80, -0.05),
                                                    false),
                                                    new BSPTree<Point2D>(Boolean.FALSE), a3, null);
         BSPTree<Point2D> a5 =
-            new BSPTree<>(buildSegment(new Point2D(0.82, -0.08),
-                                                  new Point2D(0.82, -0.18)),
+            new BSPTree<>(buildSegment(Point2D.of(0.82, -0.08),
+                                                  Point2D.of(0.82, -0.18)),
                                                   new BSPTree<Point2D>(Boolean.FALSE),
                                                   new BSPTree<Point2D>(Boolean.TRUE),
                                                   null);
         BSPTree<Point2D> a6 =
-            new BSPTree<>(buildHalfLine(new Point2D(0.82, -0.18),
-                                                   new Point2D(0.85, -0.15),
+            new BSPTree<>(buildHalfLine(Point2D.of(0.82, -0.18),
+                                                   Point2D.of(0.85, -0.15),
                                                    true),
                                                    new BSPTree<Point2D>(Boolean.FALSE), a5, null);
         BSPTree<Point2D> a7 =
-            new BSPTree<>(buildHalfLine(new Point2D(0.85, -0.05),
-                                                   new Point2D(0.82, -0.08),
+            new BSPTree<>(buildHalfLine(Point2D.of(0.85, -0.05),
+                                                   Point2D.of(0.82, -0.08),
                                                    false),
                                                    a4, a6, null);
         BSPTree<Point2D> a8 =
-            new BSPTree<>(buildLine(new Point2D(0.85, -0.25),
-                                               new Point2D(0.85,  0.05)),
+            new BSPTree<>(buildLine(Point2D.of(0.85, -0.25),
+                                               Point2D.of(0.85,  0.05)),
                                                a2, a7, null);
         BSPTree<Point2D> a9 =
-            new BSPTree<>(buildLine(new Point2D(0.90,  0.05),
-                                               new Point2D(0.90, -0.50)),
+            new BSPTree<>(buildLine(Point2D.of(0.90,  0.05),
+                                               Point2D.of(0.90, -0.50)),
                                                a8, new BSPTree<Point2D>(Boolean.FALSE), null);
 
         BSPTree<Point2D> b0 =
-            new BSPTree<>(buildSegment(new Point2D(0.92, -0.12),
-                                                  new Point2D(0.92, -0.08)),
+            new BSPTree<>(buildSegment(Point2D.of(0.92, -0.12),
+                                                  Point2D.of(0.92, -0.08)),
                                                   new BSPTree<Point2D>(Boolean.FALSE), new BSPTree<Point2D>(Boolean.TRUE),
                                                   null);
         BSPTree<Point2D> b1 =
-            new BSPTree<>(buildHalfLine(new Point2D(0.92, -0.08),
-                                                   new Point2D(0.90, -0.10),
+            new BSPTree<>(buildHalfLine(Point2D.of(0.92, -0.08),
+                                                   Point2D.of(0.90, -0.10),
                                                    true),
                                                    new BSPTree<Point2D>(Boolean.FALSE), b0, null);
         BSPTree<Point2D> b2 =
-            new BSPTree<>(buildSegment(new Point2D(0.92, -0.18),
-                                                  new Point2D(0.92, -0.12)),
+            new BSPTree<>(buildSegment(Point2D.of(0.92, -0.18),
+                                                  Point2D.of(0.92, -0.12)),
                                                   new BSPTree<Point2D>(Boolean.FALSE), new BSPTree<Point2D>(Boolean.TRUE),
                                                   null);
         BSPTree<Point2D> b3 =
-            new BSPTree<>(buildSegment(new Point2D(0.85, -0.15),
-                                                  new Point2D(0.90, -0.20)),
+            new BSPTree<>(buildSegment(Point2D.of(0.85, -0.15),
+                                                  Point2D.of(0.90, -0.20)),
                                                   new BSPTree<Point2D>(Boolean.FALSE), b2, null);
         BSPTree<Point2D> b4 =
-            new BSPTree<>(buildSegment(new Point2D(0.95, -0.15),
-                                                  new Point2D(0.85, -0.05)),
+            new BSPTree<>(buildSegment(Point2D.of(0.95, -0.15),
+                                                  Point2D.of(0.85, -0.05)),
                                                   b1, b3, null);
         BSPTree<Point2D> b5 =
-            new BSPTree<>(buildHalfLine(new Point2D(0.85, -0.05),
-                                                   new Point2D(0.85, -0.25),
+            new BSPTree<>(buildHalfLine(Point2D.of(0.85, -0.05),
+                                                   Point2D.of(0.85, -0.25),
                                                    true),
                                                    new BSPTree<Point2D>(Boolean.FALSE), b4, null);
         BSPTree<Point2D> b6 =
-            new BSPTree<>(buildLine(new Point2D(0.0, -1.10),
-                                               new Point2D(1.0, -0.10)),
+            new BSPTree<>(buildLine(Point2D.of(0.0, -1.10),
+                                               Point2D.of(1.0, -0.10)),
                                                new BSPTree<Point2D>(Boolean.FALSE), b5, null);
 
         // act
@@ -1250,38 +1250,38 @@ public void testBug20040520() {
 
         // assert
         checkPoints(Region.Location.INSIDE, c, new Point2D[] {
-            new Point2D(0.83, -0.06),
-            new Point2D(0.83, -0.15),
-            new Point2D(0.88, -0.15),
-            new Point2D(0.88, -0.09),
-            new Point2D(0.88, -0.07),
-            new Point2D(0.91, -0.18),
-            new Point2D(0.91, -0.10)
+            Point2D.of(0.83, -0.06),
+            Point2D.of(0.83, -0.15),
+            Point2D.of(0.88, -0.15),
+            Point2D.of(0.88, -0.09),
+            Point2D.of(0.88, -0.07),
+            Point2D.of(0.91, -0.18),
+            Point2D.of(0.91, -0.10)
         });
 
         checkPoints(Region.Location.OUTSIDE, c, new Point2D[] {
-            new Point2D(0.80, -0.10),
-            new Point2D(0.83, -0.50),
-            new Point2D(0.83, -0.20),
-            new Point2D(0.83, -0.02),
-            new Point2D(0.87, -0.50),
-            new Point2D(0.87, -0.20),
-            new Point2D(0.87, -0.02),
-            new Point2D(0.91, -0.20),
-            new Point2D(0.91, -0.08),
-            new Point2D(0.93, -0.15)
+            Point2D.of(0.80, -0.10),
+            Point2D.of(0.83, -0.50),
+            Point2D.of(0.83, -0.20),
+            Point2D.of(0.83, -0.02),
+            Point2D.of(0.87, -0.50),
+            Point2D.of(0.87, -0.20),
+            Point2D.of(0.87, -0.02),
+            Point2D.of(0.91, -0.20),
+            Point2D.of(0.91, -0.08),
+            Point2D.of(0.93, -0.15)
         });
 
         checkVertexLoopsEquivalent(new Point2D[][] {
             new Point2D[] {
-                new Point2D(0.85, -0.15),
-                new Point2D(0.90, -0.20),
-                new Point2D(0.92, -0.18),
-                new Point2D(0.92, -0.08),
-                new Point2D(0.90, -0.10),
-                new Point2D(0.90, -0.05),
-                new Point2D(0.82, -0.05),
-                new Point2D(0.82, -0.18),
+                Point2D.of(0.85, -0.15),
+                Point2D.of(0.90, -0.20),
+                Point2D.of(0.92, -0.18),
+                Point2D.of(0.92, -0.08),
+                Point2D.of(0.90, -0.10),
+                Point2D.of(0.90, -0.05),
+                Point2D.of(0.82, -0.05),
+                Point2D.of(0.82, -0.18),
             }
         }, c.getVertices());
     }
@@ -1290,14 +1290,14 @@ public void testBug20040520() {
     public void testBug20041003() {
         // arrange
         Line[] l = {
-            new Line(new Point2D(0.0, 0.625000007541172),
-                     new Point2D(1.0, 0.625000007541172), TEST_TOLERANCE),
-            new Line(new Point2D(-0.19204433621902645, 0.0),
-                     new Point2D(-0.19204433621902645, 1.0), TEST_TOLERANCE),
-            new Line(new Point2D(-0.40303524786887,  0.4248364535319128),
-                     new Point2D(-1.12851149797877, -0.2634107480798909), TEST_TOLERANCE),
-            new Line(new Point2D(0.0, 2.0),
-                     new Point2D(1.0, 2.0), TEST_TOLERANCE)
+            new Line(Point2D.of(0.0, 0.625000007541172),
+                     Point2D.of(1.0, 0.625000007541172), TEST_TOLERANCE),
+            new Line(Point2D.of(-0.19204433621902645, 0.0),
+                     Point2D.of(-0.19204433621902645, 1.0), TEST_TOLERANCE),
+            new Line(Point2D.of(-0.40303524786887,  0.4248364535319128),
+                     Point2D.of(-1.12851149797877, -0.2634107480798909), TEST_TOLERANCE),
+            new Line(Point2D.of(0.0, 2.0),
+                     Point2D.of(1.0, 2.0), TEST_TOLERANCE)
         };
 
         BSPTree<Point2D> node1 =
@@ -1340,181 +1340,181 @@ public void testBug20041003() {
     public void testSqueezedHexa() {
         // act
         PolygonsSet set = new PolygonsSet(TEST_TOLERANCE,
-                                          new Point2D(-6, -4), new Point2D(-8, -8), new Point2D(  8, -8),
-                                          new Point2D( 6, -4), new Point2D(10,  4), new Point2D(-10,  4));
+                                          Point2D.of(-6, -4), Point2D.of(-8, -8), Point2D.of(  8, -8),
+                                          Point2D.of( 6, -4), Point2D.of(10,  4), Point2D.of(-10,  4));
 
         // assert
-        Assert.assertEquals(Location.OUTSIDE, set.checkPoint(new Point2D(0, 6)));
+        Assert.assertEquals(Location.OUTSIDE, set.checkPoint(Point2D.of(0, 6)));
     }
 
     @Test
     public void testIssue880Simplified() {
         // arrange
         Point2D[] vertices1 = new Point2D[] {
-            new Point2D( 90.13595870833188,  38.33604606376991),
-            new Point2D( 90.14047850603913,  38.34600084496253),
-            new Point2D( 90.11045289492762,  38.36801537312368),
-            new Point2D( 90.10871471476526,  38.36878044144294),
-            new Point2D( 90.10424901707671,  38.374300101757),
-            new Point2D( 90.0979455456843,   38.373578376172475),
-            new Point2D( 90.09081227075944,  38.37526295920463),
-            new Point2D( 90.09081378927135,  38.375193883266434)
+            Point2D.of( 90.13595870833188,  38.33604606376991),
+            Point2D.of( 90.14047850603913,  38.34600084496253),
+            Point2D.of( 90.11045289492762,  38.36801537312368),
+            Point2D.of( 90.10871471476526,  38.36878044144294),
+            Point2D.of( 90.10424901707671,  38.374300101757),
+            Point2D.of( 90.0979455456843,   38.373578376172475),
+            Point2D.of( 90.09081227075944,  38.37526295920463),
+            Point2D.of( 90.09081378927135,  38.375193883266434)
         };
 
         // act
         PolygonsSet set1 = new PolygonsSet(TEST_TOLERANCE, vertices1);
 
         // assert
-        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(new Point2D(90.12,  38.32)));
-        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(new Point2D(90.135, 38.355)));
+        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(Point2D.of(90.12,  38.32)));
+        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(Point2D.of(90.135, 38.355)));
 
     }
 
     @Test
     public void testIssue880Complete() {
         Point2D[] vertices1 = new Point2D[] {
-                new Point2D( 90.08714908223715,  38.370299337260235),
-                new Point2D( 90.08709517675004,  38.3702895991413),
-                new Point2D( 90.08401538704919,  38.368849330127944),
-                new Point2D( 90.08258210430711,  38.367634558585564),
-                new Point2D( 90.08251455106665,  38.36763409247078),
-                new Point2D( 90.08106599752608,  38.36761621664249),
-                new Point2D( 90.08249585300035,  38.36753627557965),
-                new Point2D( 90.09075743352184,  38.35914647644972),
-                new Point2D( 90.09099945896571,  38.35896264724079),
-                new Point2D( 90.09269383800086,  38.34595756121246),
-                new Point2D( 90.09638631543191,  38.3457988093121),
-                new Point2D( 90.09666417351019,  38.34523360999418),
-                new Point2D( 90.1297082145872,  38.337670454923625),
-                new Point2D( 90.12971687748956,  38.337669827794684),
-                new Point2D( 90.1240820219179,  38.34328502001131),
-                new Point2D( 90.13084259656404,  38.34017811765017),
-                new Point2D( 90.13378567942857,  38.33860579180606),
-                new Point2D( 90.13519557833206,  38.33621054663689),
-                new Point2D( 90.13545616732307,  38.33614965452864),
-                new Point2D( 90.13553111202748,  38.33613962818305),
-                new Point2D( 90.1356903436448,  38.33610227127048),
-                new Point2D( 90.13576283227428,  38.33609255422783),
-                new Point2D( 90.13595870833188,  38.33604606376991),
-                new Point2D( 90.1361556630693,  38.3360024198866),
-                new Point2D( 90.13622408795709,  38.335987048115726),
-                new Point2D( 90.13696189099994,  38.33581914328681),
-                new Point2D( 90.13746655304897,  38.33616706665265),
-                new Point2D( 90.13845973716064,  38.33650776167099),
-                new Point2D( 90.13950901827667,  38.3368469456463),
-                new Point2D( 90.14393814424852,  38.337591835857495),
-                new Point2D( 90.14483839716831,  38.337076122362475),
-                new Point2D( 90.14565474433601,  38.33769000964429),
-                new Point2D( 90.14569421179482,  38.3377117256905),
-                new Point2D( 90.14577067124333,  38.33770883625908),
-                new Point2D( 90.14600350631684,  38.337714326520995),
-                new Point2D( 90.14600355139731,  38.33771435193319),
-                new Point2D( 90.14600369112401,  38.33771443882085),
-                new Point2D( 90.14600382486884,  38.33771453466096),
-                new Point2D( 90.14600395205912,  38.33771463904344),
-                new Point2D( 90.14600407214999,  38.337714751520764),
-                new Point2D( 90.14600418462749,  38.337714871611695),
-                new Point2D( 90.14600422249327,  38.337714915811034),
-                new Point2D( 90.14867838361471,  38.34113888210675),
-                new Point2D( 90.14923750157374,  38.341582537502575),
-                new Point2D( 90.14877083250991,  38.34160685841391),
-                new Point2D( 90.14816667319519,  38.34244232585684),
-                new Point2D( 90.14797696744586,  38.34248455284745),
-                new Point2D( 90.14484318014337,  38.34385573215269),
-                new Point2D( 90.14477919958296,  38.3453797747614),
-                new Point2D( 90.14202393306448,  38.34464324839456),
-                new Point2D( 90.14198920640195,  38.344651155237216),
-                new Point2D( 90.14155207025175,  38.34486424263724),
-                new Point2D( 90.1415196143314,  38.344871730519),
-                new Point2D( 90.14128611910814,  38.34500196593859),
-                new Point2D( 90.14047850603913,  38.34600084496253),
-                new Point2D( 90.14045907000337,  38.34601860032171),
-                new Point2D( 90.14039496493928,  38.346223030432384),
-                new Point2D( 90.14037626063737,  38.346240203360026),
-                new Point2D( 90.14030005823724,  38.34646920000705),
-                new Point2D( 90.13799164754806,  38.34903093011013),
-                new Point2D( 90.11045289492762,  38.36801537312368),
-                new Point2D( 90.10871471476526,  38.36878044144294),
-                new Point2D( 90.10424901707671,  38.374300101757),
-                new Point2D( 90.10263482039932,  38.37310041316073),
-                new Point2D( 90.09834601753448,  38.373615053823414),
-                new Point2D( 90.0979455456843,  38.373578376172475),
-                new Point2D( 90.09086514328669,  38.37527884194668),
-                new Point2D( 90.09084931407364,  38.37590801712463),
-                new Point2D( 90.09081227075944,  38.37526295920463),
-                new Point2D( 90.09081378927135,  38.375193883266434)
+                Point2D.of( 90.08714908223715,  38.370299337260235),
+                Point2D.of( 90.08709517675004,  38.3702895991413),
+                Point2D.of( 90.08401538704919,  38.368849330127944),
+                Point2D.of( 90.08258210430711,  38.367634558585564),
+                Point2D.of( 90.08251455106665,  38.36763409247078),
+                Point2D.of( 90.08106599752608,  38.36761621664249),
+                Point2D.of( 90.08249585300035,  38.36753627557965),
+                Point2D.of( 90.09075743352184,  38.35914647644972),
+                Point2D.of( 90.09099945896571,  38.35896264724079),
+                Point2D.of( 90.09269383800086,  38.34595756121246),
+                Point2D.of( 90.09638631543191,  38.3457988093121),
+                Point2D.of( 90.09666417351019,  38.34523360999418),
+                Point2D.of( 90.1297082145872,  38.337670454923625),
+                Point2D.of( 90.12971687748956,  38.337669827794684),
+                Point2D.of( 90.1240820219179,  38.34328502001131),
+                Point2D.of( 90.13084259656404,  38.34017811765017),
+                Point2D.of( 90.13378567942857,  38.33860579180606),
+                Point2D.of( 90.13519557833206,  38.33621054663689),
+                Point2D.of( 90.13545616732307,  38.33614965452864),
+                Point2D.of( 90.13553111202748,  38.33613962818305),
+                Point2D.of( 90.1356903436448,  38.33610227127048),
+                Point2D.of( 90.13576283227428,  38.33609255422783),
+                Point2D.of( 90.13595870833188,  38.33604606376991),
+                Point2D.of( 90.1361556630693,  38.3360024198866),
+                Point2D.of( 90.13622408795709,  38.335987048115726),
+                Point2D.of( 90.13696189099994,  38.33581914328681),
+                Point2D.of( 90.13746655304897,  38.33616706665265),
+                Point2D.of( 90.13845973716064,  38.33650776167099),
+                Point2D.of( 90.13950901827667,  38.3368469456463),
+                Point2D.of( 90.14393814424852,  38.337591835857495),
+                Point2D.of( 90.14483839716831,  38.337076122362475),
+                Point2D.of( 90.14565474433601,  38.33769000964429),
+                Point2D.of( 90.14569421179482,  38.3377117256905),
+                Point2D.of( 90.14577067124333,  38.33770883625908),
+                Point2D.of( 90.14600350631684,  38.337714326520995),
+                Point2D.of( 90.14600355139731,  38.33771435193319),
+                Point2D.of( 90.14600369112401,  38.33771443882085),
+                Point2D.of( 90.14600382486884,  38.33771453466096),
+                Point2D.of( 90.14600395205912,  38.33771463904344),
+                Point2D.of( 90.14600407214999,  38.337714751520764),
+                Point2D.of( 90.14600418462749,  38.337714871611695),
+                Point2D.of( 90.14600422249327,  38.337714915811034),
+                Point2D.of( 90.14867838361471,  38.34113888210675),
+                Point2D.of( 90.14923750157374,  38.341582537502575),
+                Point2D.of( 90.14877083250991,  38.34160685841391),
+                Point2D.of( 90.14816667319519,  38.34244232585684),
+                Point2D.of( 90.14797696744586,  38.34248455284745),
+                Point2D.of( 90.14484318014337,  38.34385573215269),
+                Point2D.of( 90.14477919958296,  38.3453797747614),
+                Point2D.of( 90.14202393306448,  38.34464324839456),
+                Point2D.of( 90.14198920640195,  38.344651155237216),
+                Point2D.of( 90.14155207025175,  38.34486424263724),
+                Point2D.of( 90.1415196143314,  38.344871730519),
+                Point2D.of( 90.14128611910814,  38.34500196593859),
+                Point2D.of( 90.14047850603913,  38.34600084496253),
+                Point2D.of( 90.14045907000337,  38.34601860032171),
+                Point2D.of( 90.14039496493928,  38.346223030432384),
+                Point2D.of( 90.14037626063737,  38.346240203360026),
+                Point2D.of( 90.14030005823724,  38.34646920000705),
+                Point2D.of( 90.13799164754806,  38.34903093011013),
+                Point2D.of( 90.11045289492762,  38.36801537312368),
+                Point2D.of( 90.10871471476526,  38.36878044144294),
+                Point2D.of( 90.10424901707671,  38.374300101757),
+                Point2D.of( 90.10263482039932,  38.37310041316073),
+                Point2D.of( 90.09834601753448,  38.373615053823414),
+                Point2D.of( 90.0979455456843,  38.373578376172475),
+                Point2D.of( 90.09086514328669,  38.37527884194668),
+                Point2D.of( 90.09084931407364,  38.37590801712463),
+                Point2D.of( 90.09081227075944,  38.37526295920463),
+                Point2D.of( 90.09081378927135,  38.375193883266434)
         };
         PolygonsSet set1 = new PolygonsSet(1.0e-8, vertices1);
-        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(new Point2D(90.0905,  38.3755)));
-        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(new Point2D(90.09084, 38.3755)));
-        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(new Point2D(90.0913,  38.3755)));
-        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(new Point2D(90.1042,  38.3739)));
-        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(new Point2D(90.1111,  38.3673)));
-        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(new Point2D(90.0959,  38.3457)));
+        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(Point2D.of(90.0905,  38.3755)));
+        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(Point2D.of(90.09084, 38.3755)));
+        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(Point2D.of(90.0913,  38.3755)));
+        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(Point2D.of(90.1042,  38.3739)));
+        Assert.assertEquals(Location.INSIDE,  set1.checkPoint(Point2D.of(90.1111,  38.3673)));
+        Assert.assertEquals(Location.OUTSIDE, set1.checkPoint(Point2D.of(90.0959,  38.3457)));
 
         Point2D[] vertices2 = new Point2D[] {
-                new Point2D( 90.13067558880044,  38.36977255037573),
-                new Point2D( 90.12907570488,  38.36817308242706),
-                new Point2D( 90.1342774136516,  38.356886880294724),
-                new Point2D( 90.13090330629757,  38.34664392676211),
-                new Point2D( 90.13078571364593,  38.344904617518466),
-                new Point2D( 90.1315602208914,  38.3447185040846),
-                new Point2D( 90.1316336226821,  38.34470643148342),
-                new Point2D( 90.134020944832,  38.340936644972885),
-                new Point2D( 90.13912536387306,  38.335497255122334),
-                new Point2D( 90.1396178806582,  38.334878075552126),
-                new Point2D( 90.14083049696671,  38.33316530644106),
-                new Point2D( 90.14145252901329,  38.33152722916191),
-                new Point2D( 90.1404779335565,  38.32863516047786),
-                new Point2D( 90.14282712131586,  38.327504432532066),
-                new Point2D( 90.14616669875488,  38.3237354115015),
-                new Point2D( 90.14860976050608,  38.315714862457924),
-                new Point2D( 90.14999277782437,  38.3164932507504),
-                new Point2D( 90.15005207194997,  38.316534677663356),
-                new Point2D( 90.15508513859612,  38.31878731691609),
-                new Point2D( 90.15919938519221,  38.31852743183782),
-                new Point2D( 90.16093758658837,  38.31880662005153),
-                new Point2D( 90.16099420184912,  38.318825953291594),
-                new Point2D( 90.1665411125756,  38.31859497874757),
-                new Point2D( 90.16999653861313,  38.32505772048029),
-                new Point2D( 90.17475243391698,  38.32594398441148),
-                new Point2D( 90.17940844844992,  38.327427213761325),
-                new Point2D( 90.20951909541378,  38.330616833491774),
-                new Point2D( 90.2155400467941,  38.331746223670336),
-                new Point2D( 90.21559881391778,  38.33175551425302),
-                new Point2D( 90.21916646426041,  38.332584299620805),
-                new Point2D( 90.23863749852285,  38.34778978875795),
-                new Point2D( 90.25459855175802,  38.357790570608984),
-                new Point2D( 90.25964298227257,  38.356918010203174),
-                new Point2D( 90.26024593994703,  38.361692743151366),
-                new Point2D( 90.26146187570015,  38.36311080550837),
-                new Point2D( 90.26614159359622,  38.36510808579902),
-                new Point2D( 90.26621342936448,  38.36507942500333),
-                new Point2D( 90.26652190211962,  38.36494042196722),
-                new Point2D( 90.26621240678867,  38.365113172030874),
-                new Point2D( 90.26614057102057,  38.365141832826794),
-                new Point2D( 90.26380080055299,  38.3660381760273),
-                new Point2D( 90.26315345241,  38.36670658276421),
-                new Point2D( 90.26251574942881,  38.367490323488084),
-                new Point2D( 90.26247873448426,  38.36755266444749),
-                new Point2D( 90.26234628016698,  38.36787989125406),
-                new Point2D( 90.26214559424784,  38.36945909356126),
-                new Point2D( 90.25861728442555,  38.37200753430875),
-                new Point2D( 90.23905557537864,  38.375405314295904),
-                new Point2D( 90.22517251874075,  38.38984691662256),
-                new Point2D( 90.22549955153215,  38.3911564273979),
-                new Point2D( 90.22434386063355,  38.391476432092134),
-                new Point2D( 90.22147729457276,  38.39134652252034),
-                new Point2D( 90.22142070120117,  38.391349167741964),
-                new Point2D( 90.20665060751588,  38.39475580900313),
-                new Point2D( 90.20042268367109,  38.39842558622888),
-                new Point2D( 90.17423771242085,  38.402727751805344),
-                new Point2D( 90.16756796257476,  38.40913898597597),
-                new Point2D( 90.16728283954308,  38.411255399912875),
-                new Point2D( 90.16703538220418,  38.41136059866693),
-                new Point2D( 90.16725865657685,  38.41013618805954),
-                new Point2D( 90.16746107640665,  38.40902614307544),
-                new Point2D( 90.16122795307462,  38.39773101873203)
+                Point2D.of( 90.13067558880044,  38.36977255037573),
+                Point2D.of( 90.12907570488,  38.36817308242706),
+                Point2D.of( 90.1342774136516,  38.356886880294724),
+                Point2D.of( 90.13090330629757,  38.34664392676211),
+                Point2D.of( 90.13078571364593,  38.344904617518466),
+                Point2D.of( 90.1315602208914,  38.3447185040846),
+                Point2D.of( 90.1316336226821,  38.34470643148342),
+                Point2D.of( 90.134020944832,  38.340936644972885),
+                Point2D.of( 90.13912536387306,  38.335497255122334),
+                Point2D.of( 90.1396178806582,  38.334878075552126),
+                Point2D.of( 90.14083049696671,  38.33316530644106),
+                Point2D.of( 90.14145252901329,  38.33152722916191),
+                Point2D.of( 90.1404779335565,  38.32863516047786),
+                Point2D.of( 90.14282712131586,  38.327504432532066),
+                Point2D.of( 90.14616669875488,  38.3237354115015),
+                Point2D.of( 90.14860976050608,  38.315714862457924),
+                Point2D.of( 90.14999277782437,  38.3164932507504),
+                Point2D.of( 90.15005207194997,  38.316534677663356),
+                Point2D.of( 90.15508513859612,  38.31878731691609),
+                Point2D.of( 90.15919938519221,  38.31852743183782),
+                Point2D.of( 90.16093758658837,  38.31880662005153),
+                Point2D.of( 90.16099420184912,  38.318825953291594),
+                Point2D.of( 90.1665411125756,  38.31859497874757),
+                Point2D.of( 90.16999653861313,  38.32505772048029),
+                Point2D.of( 90.17475243391698,  38.32594398441148),
+                Point2D.of( 90.17940844844992,  38.327427213761325),
+                Point2D.of( 90.20951909541378,  38.330616833491774),
+                Point2D.of( 90.2155400467941,  38.331746223670336),
+                Point2D.of( 90.21559881391778,  38.33175551425302),
+                Point2D.of( 90.21916646426041,  38.332584299620805),
+                Point2D.of( 90.23863749852285,  38.34778978875795),
+                Point2D.of( 90.25459855175802,  38.357790570608984),
+                Point2D.of( 90.25964298227257,  38.356918010203174),
+                Point2D.of( 90.26024593994703,  38.361692743151366),
+                Point2D.of( 90.26146187570015,  38.36311080550837),
+                Point2D.of( 90.26614159359622,  38.36510808579902),
+                Point2D.of( 90.26621342936448,  38.36507942500333),
+                Point2D.of( 90.26652190211962,  38.36494042196722),
+                Point2D.of( 90.26621240678867,  38.365113172030874),
+                Point2D.of( 90.26614057102057,  38.365141832826794),
+                Point2D.of( 90.26380080055299,  38.3660381760273),
+                Point2D.of( 90.26315345241,  38.36670658276421),
+                Point2D.of( 90.26251574942881,  38.367490323488084),
+                Point2D.of( 90.26247873448426,  38.36755266444749),
+                Point2D.of( 90.26234628016698,  38.36787989125406),
+                Point2D.of( 90.26214559424784,  38.36945909356126),
+                Point2D.of( 90.25861728442555,  38.37200753430875),
+                Point2D.of( 90.23905557537864,  38.375405314295904),
+                Point2D.of( 90.22517251874075,  38.38984691662256),
+                Point2D.of( 90.22549955153215,  38.3911564273979),
+                Point2D.of( 90.22434386063355,  38.391476432092134),
+                Point2D.of( 90.22147729457276,  38.39134652252034),
+                Point2D.of( 90.22142070120117,  38.391349167741964),
+                Point2D.of( 90.20665060751588,  38.39475580900313),
+                Point2D.of( 90.20042268367109,  38.39842558622888),
+                Point2D.of( 90.17423771242085,  38.402727751805344),
+                Point2D.of( 90.16756796257476,  38.40913898597597),
+                Point2D.of( 90.16728283954308,  38.411255399912875),
+                Point2D.of( 90.16703538220418,  38.41136059866693),
+                Point2D.of( 90.16725865657685,  38.41013618805954),
+                Point2D.of( 90.16746107640665,  38.40902614307544),
+                Point2D.of( 90.16122795307462,  38.39773101873203)
         };
         PolygonsSet set2 = new PolygonsSet(1.0e-8, vertices2);
         PolygonsSet set  = (PolygonsSet) new
@@ -1553,17 +1553,17 @@ public void testWrongUsage() {
     public void testIssue1162() {
         // arrange
         PolygonsSet p = new PolygonsSet(TEST_TOLERANCE,
-                                                new Point2D(4.267199999996532, -11.928637756014894),
-                                                new Point2D(4.267200000026445, -14.12360595809307),
-                                                new Point2D(9.144000000273694, -14.12360595809307),
-                                                new Point2D(9.144000000233383, -11.928637756020067));
+                                                Point2D.of(4.267199999996532, -11.928637756014894),
+                                                Point2D.of(4.267200000026445, -14.12360595809307),
+                                                Point2D.of(9.144000000273694, -14.12360595809307),
+                                                Point2D.of(9.144000000233383, -11.928637756020067));
 
         PolygonsSet w = new PolygonsSet(TEST_TOLERANCE,
-                                                new Point2D(2.56735636510452512E-9, -11.933116461089332),
-                                                new Point2D(2.56735636510452512E-9, -12.393225665247766),
-                                                new Point2D(2.56735636510452512E-9, -27.785625665247778),
-                                                new Point2D(4.267200000030211,      -27.785625665247778),
-                                                new Point2D(4.267200000030211,      -11.933116461089332));
+                                                Point2D.of(2.56735636510452512E-9, -11.933116461089332),
+                                                Point2D.of(2.56735636510452512E-9, -12.393225665247766),
+                                                Point2D.of(2.56735636510452512E-9, -27.785625665247778),
+                                                Point2D.of(4.267200000030211,      -27.785625665247778),
+                                                Point2D.of(4.267200000030211,      -11.933116461089332));
 
         // act/assert
         Assert.assertFalse(p.contains(w));
@@ -1575,10 +1575,10 @@ public void testThinRectangle_toleranceLessThanWidth_resultIsAccurate() {
 
         // arrange
         RegionFactory<Point2D> factory = new RegionFactory<>();
-        Point2D pA = new Point2D(0.0,        1.0);
-        Point2D pB = new Point2D(0.0,        0.0);
-        Point2D pC = new Point2D(1.0 / 64.0, 0.0);
-        Point2D pD = new Point2D(1.0 / 64.0, 1.0);
+        Point2D pA = Point2D.of(0.0,        1.0);
+        Point2D pB = Point2D.of(0.0,        0.0);
+        Point2D pC = Point2D.of(1.0 / 64.0, 0.0);
+        Point2D pD = Point2D.of(1.0 / 64.0, 1.0);
 
         // if tolerance is smaller than rectangle width, the rectangle is computed accurately
         Hyperplane<Point2D>[] h1 = new Line[] {
@@ -1605,10 +1605,10 @@ public void testThinRectangle_toleranceGreaterThanWidth_resultIsDegenerate() {
 
         // arrange
         RegionFactory<Point2D> factory = new RegionFactory<>();
-        Point2D pA = new Point2D(0.0,        1.0);
-        Point2D pB = new Point2D(0.0,        0.0);
-        Point2D pC = new Point2D(1.0 / 64.0, 0.0);
-        Point2D pD = new Point2D(1.0 / 64.0, 1.0);
+        Point2D pA = Point2D.of(0.0,        1.0);
+        Point2D pB = Point2D.of(0.0,        0.0);
+        Point2D pC = Point2D.of(1.0 / 64.0, 0.0);
+        Point2D pD = Point2D.of(1.0 / 64.0, 1.0);
 
         Hyperplane<Point2D>[] h2 = new Line[] {
                 new Line(pA, pB, 1.0 / 16),
@@ -1629,18 +1629,18 @@ public void testThinRectangle_toleranceGreaterThanWidth_resultIsDegenerate() {
     public void testInconsistentHyperplanes() {
         // act
         double tolerance = TEST_TOLERANCE;
-        new RegionFactory<Point2D>().buildConvex(new Line(new Point2D(0, 0), new Point2D(0, 1), tolerance),
-                                                     new Line(new Point2D(1, 1), new Point2D(1, 0), tolerance));
+        new RegionFactory<Point2D>().buildConvex(new Line(Point2D.of(0, 0), Point2D.of(0, 1), tolerance),
+                                                     new Line(Point2D.of(1, 1), Point2D.of(1, 0), tolerance));
     }
 
     @Test
     public void testBoundarySimplification() {
         // a simple square will result in a 4 cuts and 5 leafs tree
         PolygonsSet square = new PolygonsSet(TEST_TOLERANCE,
-                                             new Point2D(0, 0),
-                                             new Point2D(1, 0),
-                                             new Point2D(1, 1),
-                                             new Point2D(0, 1));
+                                             Point2D.of(0, 0),
+                                             Point2D.of(1, 0),
+                                             Point2D.of(1, 1),
+                                             Point2D.of(0, 1));
         Point2D[][] squareBoundary = square.getVertices();
         Assert.assertEquals(1, squareBoundary.length);
         Assert.assertEquals(4, squareBoundary[0].length);
@@ -1651,7 +1651,7 @@ public void testBoundarySimplification() {
 
         // splitting the square in two halves increases the BSP tree
         // with 3 more cuts and 3 more leaf nodes
-        SubLine cut = new Line(new Point2D(0.5, 0.5), 0.0, square.getTolerance()).wholeHyperplane();
+        SubLine cut = new Line(Point2D.of(0.5, 0.5), 0.0, square.getTolerance()).wholeHyperplane();
         PolygonsSet splitSquare = new PolygonsSet(square.getTree(false).split(cut),
                                                   square.getTolerance());
         Counter splitSquareCount = new Counter();
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
index d5ca735..f9c71e2 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
@@ -23,20 +23,20 @@
 
     @Test
     public void testDistance() {
-        Point2D start = new Point2D(2, 2);
-        Point2D end = new Point2D(-2, -2);
+        Point2D start = Point2D.of(2, 2);
+        Point2D end = Point2D.of(-2, -2);
         Segment segment = new Segment(start, end, new Line(start, end, 1.0e-10));
 
         // distance to center of segment
-        Assert.assertEquals(Math.sqrt(2), segment.distance(new Point2D(1, -1)), 1.0e-10);
+        Assert.assertEquals(Math.sqrt(2), segment.distance(Point2D.of(1, -1)), 1.0e-10);
 
         // distance a point on segment
-        Assert.assertEquals(Math.sin(Math.PI / 4.0), segment.distance(new Point2D(0, -1)), 1.0e-10);
+        Assert.assertEquals(Math.sin(Math.PI / 4.0), segment.distance(Point2D.of(0, -1)), 1.0e-10);
 
         // distance to end point
-        Assert.assertEquals(Math.sqrt(8), segment.distance(new Point2D(0, 4)), 1.0e-10);
+        Assert.assertEquals(Math.sqrt(8), segment.distance(Point2D.of(0, 4)), 1.0e-10);
 
         // distance to start point
-        Assert.assertEquals(Math.sqrt(8), segment.distance(new Point2D(0, -4)), 1.0e-10);
+        Assert.assertEquals(Math.sqrt(8), segment.distance(Point2D.of(0, -4)), 1.0e-10);
     }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SubLineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SubLineTest.java
index 246491a..94a282d 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SubLineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SubLineTest.java
@@ -28,19 +28,19 @@
 
     @Test
     public void testEndPoints() {
-        Point2D p1 = new Point2D(-1, -7);
-        Point2D p2 = new Point2D(7, -1);
+        Point2D p1 = Point2D.of(-1, -7);
+        Point2D p2 = Point2D.of(7, -1);
         Segment segment = new Segment(p1, p2, new Line(p1, p2, 1.0e-10));
         SubLine sub = new SubLine(segment);
         List<Segment> segments = sub.getSegments();
         Assert.assertEquals(1, segments.size());
-        Assert.assertEquals(0.0, new Point2D(-1, -7).distance(segments.get(0).getStart()), 1.0e-10);
-        Assert.assertEquals(0.0, new Point2D( 7, -1).distance(segments.get(0).getEnd()), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(-1, -7).distance(segments.get(0).getStart()), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of( 7, -1).distance(segments.get(0).getEnd()), 1.0e-10);
     }
 
     @Test
     public void testNoEndPoints() {
-        SubLine wholeLine = new Line(new Point2D(-1, 7), new Point2D(7, 1), 1.0e-10).wholeHyperplane();
+        SubLine wholeLine = new Line(Point2D.of(-1, 7), Point2D.of(7, 1), 1.0e-10).wholeHyperplane();
         List<Segment> segments = wholeLine.getSegments();
         Assert.assertEquals(1, segments.size());
         Assert.assertTrue(Double.isInfinite(segments.get(0).getStart().getX()) &&
@@ -55,7 +55,7 @@ public void testNoEndPoints() {
 
     @Test
     public void testNoSegments() {
-        SubLine empty = new SubLine(new Line(new Point2D(-1, -7), new Point2D(7, -1), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point2D.of(-1, -7), Point2D.of(7, -1), 1.0e-10),
                                     new RegionFactory<Point1D>().getComplement(new IntervalsSet(1.0e-10)));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(0, segments.size());
@@ -63,7 +63,7 @@ public void testNoSegments() {
 
     @Test
     public void testSeveralSegments() {
-        SubLine twoSubs = new SubLine(new Line(new Point2D(-1, -7), new Point2D(7, -1), 1.0e-10),
+        SubLine twoSubs = new SubLine(new Line(Point2D.of(-1, -7), Point2D.of(7, -1), 1.0e-10),
                                     new RegionFactory<Point1D>().union(new IntervalsSet(1, 2, 1.0e-10),
                                                                            new IntervalsSet(3, 4, 1.0e-10)));
         List<Segment> segments = twoSubs.getSegments();
@@ -72,7 +72,7 @@ public void testSeveralSegments() {
 
     @Test
     public void testHalfInfiniteNeg() {
-        SubLine empty = new SubLine(new Line(new Point2D(-1, -7), new Point2D(7, -1), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point2D.of(-1, -7), Point2D.of(7, -1), 1.0e-10),
                                     new IntervalsSet(Double.NEGATIVE_INFINITY, 0.0, 1.0e-10));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(1, segments.size());
@@ -80,16 +80,16 @@ public void testHalfInfiniteNeg() {
                           segments.get(0).getStart().getX() < 0);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getStart().getY()) &&
                           segments.get(0).getStart().getY() < 0);
-        Assert.assertEquals(0.0, new Point2D(3, -4).distance(segments.get(0).getEnd()), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(3, -4).distance(segments.get(0).getEnd()), 1.0e-10);
     }
 
     @Test
     public void testHalfInfinitePos() {
-        SubLine empty = new SubLine(new Line(new Point2D(-1, -7), new Point2D(7, -1), 1.0e-10),
+        SubLine empty = new SubLine(new Line(Point2D.of(-1, -7), Point2D.of(7, -1), 1.0e-10),
                                     new IntervalsSet(0.0, Double.POSITIVE_INFINITY, 1.0e-10));
         List<Segment> segments = empty.getSegments();
         Assert.assertEquals(1, segments.size());
-        Assert.assertEquals(0.0, new Point2D(3, -4).distance(segments.get(0).getStart()), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(3, -4).distance(segments.get(0).getStart()), 1.0e-10);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getEnd().getX()) &&
                           segments.get(0).getEnd().getX() > 0);
         Assert.assertTrue(Double.isInfinite(segments.get(0).getEnd().getY()) &&
@@ -98,56 +98,56 @@ public void testHalfInfinitePos() {
 
     @Test
     public void testIntersectionInsideInside() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(3, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 2), 1.0e-10);
-        Assert.assertEquals(0.0, new Point2D(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
-        Assert.assertEquals(0.0, new Point2D(2, 1).distance(sub1.intersection(sub2, false)), 1.0e-12);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(3, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 2), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        Assert.assertEquals(0.0, Point2D.of(2, 1).distance(sub1.intersection(sub2, false)), 1.0e-12);
     }
 
     @Test
     public void testIntersectionInsideBoundary() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(3, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 1), 1.0e-10);
-        Assert.assertEquals(0.0, new Point2D(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(3, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 1), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionInsideOutside() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(3, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(3, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionBoundaryBoundary() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(2, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 1), 1.0e-10);
-        Assert.assertEquals(0.0, new Point2D(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(2, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 1), 1.0e-10);
+        Assert.assertEquals(0.0, Point2D.of(2, 1).distance(sub1.intersection(sub2, true)),  1.0e-12);
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionBoundaryOutside() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(2, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(2, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionOutsideOutside() {
-        SubLine sub1 = new SubLine(new Point2D(1, 1), new Point2D(1.5, 1), 1.0e-10);
-        SubLine sub2 = new SubLine(new Point2D(2, 0), new Point2D(2, 0.5), 1.0e-10);
+        SubLine sub1 = new SubLine(Point2D.of(1, 1), Point2D.of(1.5, 1), 1.0e-10);
+        SubLine sub2 = new SubLine(Point2D.of(2, 0), Point2D.of(2, 0.5), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
 
     @Test
     public void testIntersectionParallel() {
-        final SubLine sub1 = new SubLine(new Point2D(0, 1), new Point2D(0, 2), 1.0e-10);
-        final SubLine sub2 = new SubLine(new Point2D(66, 3), new Point2D(66, 4), 1.0e-10);
+        final SubLine sub1 = new SubLine(Point2D.of(0, 1), Point2D.of(0, 2), 1.0e-10);
+        final SubLine sub2 = new SubLine(Point2D.of(66, 3), Point2D.of(66, 4), 1.0e-10);
         Assert.assertNull(sub1.intersection(sub2, true));
         Assert.assertNull(sub1.intersection(sub2, false));
     }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
index 7882e7d..83d56a1 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
@@ -420,7 +420,7 @@ public void testEquals() {
     public void testToString() {
         // arrange
         Vector2D v = Vector2D.of(1, 2);
-        Pattern pattern = Pattern.compile("\\{1.{0,2}; 2.{0,2}\\}");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
 
         // act
         String str = v.toString();
@@ -430,6 +430,26 @@ public void testToString() {
                     pattern.matcher(str).matches());
     }
 
+    @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector2D.parse("(1, 2)"), 1, 2);
+        checkVector(Vector2D.parse("(-1, -2)"), -1, -2);
+
+        checkVector(Vector2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3);
+
+        checkVector(Vector2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY);
+
+        checkVector(Vector2D.parse(Vector2D.ZERO.toString()), 0, 0);
+        checkVector(Vector2D.parse(Vector2D.MINUS_X.toString()), -1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector2D.parse("abc");
+    }
+
     @Test
     public void testOf() {
         // act/assert
@@ -462,7 +482,6 @@ public void testOf_arrayArg_invalidDimensions() {
         // act/assert
         Vector2D.of(new double[] {0.0 });
     }
-
     @Test
     public void testLinearCombination1() {
         // arrange
diff --git a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
index 1e7c0b9..00947b0 100644
--- a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
+++ b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
@@ -85,10 +85,10 @@ public void testTwoPoints() {
     @Test
     public void testAllIdentical() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(1, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(1, 1));
 
         final ConvexHull2D hull = generator.generate(points);
         Assert.assertTrue(hull.getVertices().length == 1);
@@ -110,11 +110,11 @@ public void testConvexHull() {
     @Test
     public void testCollinearPoints() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(4, 1));
-        points.add(new Point2D(10, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(4, 1));
+        points.add(Point2D.of(10, 1));
 
         final ConvexHull2D hull = generator.generate(points);
         checkConvexHull(points, hull);
@@ -123,11 +123,11 @@ public void testCollinearPoints() {
     @Test
     public void testCollinearPointsReverse() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(10, 1));
-        points.add(new Point2D(4, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(10, 1));
+        points.add(Point2D.of(4, 1));
 
         final ConvexHull2D hull = generator.generate(points);
         checkConvexHull(points, hull);
@@ -136,11 +136,11 @@ public void testCollinearPointsReverse() {
     @Test
     public void testCollinearPointsIncluded() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(4, 1));
-        points.add(new Point2D(10, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(4, 1));
+        points.add(Point2D.of(10, 1));
 
         final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
         checkConvexHull(points, hull, true);
@@ -149,11 +149,11 @@ public void testCollinearPointsIncluded() {
     @Test
     public void testCollinearPointsIncludedReverse() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(10, 1));
-        points.add(new Point2D(4, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(10, 1));
+        points.add(Point2D.of(4, 1));
 
         final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
         checkConvexHull(points, hull, true);
@@ -162,11 +162,11 @@ public void testCollinearPointsIncludedReverse() {
     @Test
     public void testIdenticalPoints() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(4, 1));
-        points.add(new Point2D(1, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(4, 1));
+        points.add(Point2D.of(1, 1));
 
         final ConvexHull2D hull = generator.generate(points);
         checkConvexHull(points, hull);
@@ -175,11 +175,11 @@ public void testIdenticalPoints() {
     @Test
     public void testIdenticalPoints2() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(4, 1));
-        points.add(new Point2D(1, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(4, 1));
+        points.add(Point2D.of(1, 1));
 
         final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
         checkConvexHull(points, hull, true);
@@ -188,11 +188,11 @@ public void testIdenticalPoints2() {
     @Test
     public void testClosePoints() {
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(2, 2));
-        points.add(new Point2D(2, 4));
-        points.add(new Point2D(4, 1));
-        points.add(new Point2D(1.00001, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(2, 2));
+        points.add(Point2D.of(2, 4));
+        points.add(Point2D.of(4, 1));
+        points.add(Point2D.of(1.00001, 1));
 
         final ConvexHull2D hull = generator.generate(points);
         checkConvexHull(points, hull);
@@ -203,16 +203,16 @@ public void testCollinearPointOnExistingBoundary() {
         // MATH-1135: check that collinear points on the hull are handled correctly
         //            when only a minimal hull shall be constructed
         final Collection<Point2D> points = new ArrayList<>();
-        points.add(new Point2D(7.3152, 34.7472));
-        points.add(new Point2D(6.400799999999997, 34.747199999999985));
-        points.add(new Point2D(5.486399999999997, 34.7472));
-        points.add(new Point2D(4.876799999999999, 34.7472));
-        points.add(new Point2D(4.876799999999999, 34.1376));
-        points.add(new Point2D(4.876799999999999, 30.48));
-        points.add(new Point2D(6.0959999999999965, 30.48));
-        points.add(new Point2D(6.0959999999999965, 34.1376));
-        points.add(new Point2D(7.315199999999996, 34.1376));
-        points.add(new Point2D(7.3152, 30.48));
+        points.add(Point2D.of(7.3152, 34.7472));
+        points.add(Point2D.of(6.400799999999997, 34.747199999999985));
+        points.add(Point2D.of(5.486399999999997, 34.7472));
+        points.add(Point2D.of(4.876799999999999, 34.7472));
+        points.add(Point2D.of(4.876799999999999, 34.1376));
+        points.add(Point2D.of(4.876799999999999, 30.48));
+        points.add(Point2D.of(6.0959999999999965, 30.48));
+        points.add(Point2D.of(6.0959999999999965, 34.1376));
+        points.add(Point2D.of(7.315199999999996, 34.1376));
+        points.add(Point2D.of(7.3152, 30.48));
 
         final ConvexHull2D hull = createConvexHullGenerator(false).generate(points);
         checkConvexHull(points, hull);
@@ -227,10 +227,10 @@ public void testCollinearPointsInAnyOrder() {
         List<Point2D> points = new ArrayList<>();
 
         // first case: 3 points are collinear
-        points.add(new Point2D(16.078200000000184, -36.52519999989808));
-        points.add(new Point2D(19.164300000000186, -36.52519999989808));
-        points.add(new Point2D(19.1643, -25.28136477910407));
-        points.add(new Point2D(19.1643, -17.678400000004157));
+        points.add(Point2D.of(16.078200000000184, -36.52519999989808));
+        points.add(Point2D.of(19.164300000000186, -36.52519999989808));
+        points.add(Point2D.of(19.1643, -25.28136477910407));
+        points.add(Point2D.of(19.1643, -17.678400000004157));
 
         ConvexHull2D hull = createConvexHullGenerator(false).generate(points);
         checkConvexHull(points, hull);
@@ -241,15 +241,15 @@ public void testCollinearPointsInAnyOrder() {
         points.clear();
 
         // second case: multiple points are collinear
-        points.add(new Point2D(0, -29.959696875));
-        points.add(new Point2D(0, -31.621809375));
-        points.add(new Point2D(0, -28.435696875));
-        points.add(new Point2D(0, -33.145809375));
-        points.add(new Point2D(3.048, -33.145809375));
-        points.add(new Point2D(3.048, -31.621809375));
-        points.add(new Point2D(3.048, -29.959696875));
-        points.add(new Point2D(4.572, -33.145809375));
-        points.add(new Point2D(4.572, -28.435696875));
+        points.add(Point2D.of(0, -29.959696875));
+        points.add(Point2D.of(0, -31.621809375));
+        points.add(Point2D.of(0, -28.435696875));
+        points.add(Point2D.of(0, -33.145809375));
+        points.add(Point2D.of(3.048, -33.145809375));
+        points.add(Point2D.of(3.048, -31.621809375));
+        points.add(Point2D.of(3.048, -29.959696875));
+        points.add(Point2D.of(4.572, -33.145809375));
+        points.add(Point2D.of(4.572, -28.435696875));
 
         hull = createConvexHullGenerator(false).generate(points);
         checkConvexHull(points, hull);
@@ -315,26 +315,26 @@ public void testIssue1123() {
                 { 11, -1 }, { 11, 0 }, { 11, 1 } };
 
         for (int[] line : data) {
-            points.add(new Point2D(line[0], line[1]));
+            points.add(Point2D.of(line[0], line[1]));
         }
 
         Point2D[] referenceHull = new Point2D[] {
-            new Point2D(-11.0, -1.0),
-            new Point2D(-10.0, -3.0),
-            new Point2D( -6.0, -7.0),
-            new Point2D( -3.0, -8.0),
-            new Point2D(  3.0, -8.0),
-            new Point2D(  6.0, -7.0),
-            new Point2D( 10.0, -3.0),
-            new Point2D( 11.0, -1.0),
-            new Point2D( 11.0,  1.0),
-            new Point2D( 10.0,  3.0),
-            new Point2D(  6.0,  7.0),
-            new Point2D(  3.0,  8.0),
-            new Point2D( -3.0,  8.0),
-            new Point2D( -6.0,  7.0),
-            new Point2D(-10.0,  3.0),
-            new Point2D(-11.0,  1.0),
+            Point2D.of(-11.0, -1.0),
+            Point2D.of(-10.0, -3.0),
+            Point2D.of( -6.0, -7.0),
+            Point2D.of( -3.0, -8.0),
+            Point2D.of(  3.0, -8.0),
+            Point2D.of(  6.0, -7.0),
+            Point2D.of( 10.0, -3.0),
+            Point2D.of( 11.0, -1.0),
+            Point2D.of( 11.0,  1.0),
+            Point2D.of( 10.0,  3.0),
+            Point2D.of(  6.0,  7.0),
+            Point2D.of(  3.0,  8.0),
+            Point2D.of( -3.0,  8.0),
+            Point2D.of( -6.0,  7.0),
+            Point2D.of(-10.0,  3.0),
+            Point2D.of(-11.0,  1.0),
         };
 
         ConvexHull2D convHull = generator.generate(points);
@@ -361,7 +361,7 @@ public void testIssue1123() {
         List<Point2D> points = new ArrayList<>(size);
         // fill the cloud with a random distribution of points
         for (int i = 0; i < size; i++) {
-            points.add(new Point2D(random.nextDouble() * 2.0 - 1.0, random.nextDouble() * 2.0 - 1.0));
+            points.add(Point2D.of(random.nextDouble() * 2.0 - 1.0, random.nextDouble() * 2.0 - 1.0));
         }
         return points;
     }
diff --git a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/MonotoneChainTest.java b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/MonotoneChainTest.java
index a19e99a..86f513d 100644
--- a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/MonotoneChainTest.java
+++ b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/euclidean/twod/hull/MonotoneChainTest.java
@@ -38,14 +38,14 @@ protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeColline
     public void testConvergenceException() {
         final Collection<Point2D> points = new ArrayList<>();
 
-        points.add(new Point2D(1, 1));
-        points.add(new Point2D(1, 5));
-        points.add(new Point2D(0, 7));
-        points.add(new Point2D(1, 10));
-        points.add(new Point2D(1, 20));
-        points.add(new Point2D(20, 20));
-        points.add(new Point2D(20, 40));
-        points.add(new Point2D(40, 1));
+        points.add(Point2D.of(1, 1));
+        points.add(Point2D.of(1, 5));
+        points.add(Point2D.of(0, 7));
+        points.add(Point2D.of(1, 10));
+        points.add(Point2D.of(1, 20));
+        points.add(Point2D.of(20, 20));
+        points.add(Point2D.of(20, 40));
+        points.add(Point2D.of(40, 1));
 
         @SuppressWarnings("unused")
         final ConvexHull2D hull = new MonotoneChain(true, 2).generate(points);
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/SphericalCoordinates.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/SphericalCoordinates.java
index d8dc614..d9d959f 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/SphericalCoordinates.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/SphericalCoordinates.java
@@ -110,7 +110,7 @@ public SphericalCoordinates(final double r, final double theta, final double phi
         this.phi   = phi;
 
         // Cartesian coordinates
-        this.v  = new Vector3D(r * cosTheta * sinPhi,
+        this.v  = Vector3D.of(r * cosTheta * sinPhi,
                                r * sinTheta * sinPhi,
                                r * cosPhi);
 
@@ -386,7 +386,7 @@ private Object writeReplace() {
          * @return replacement {@link SphericalCoordinates}
          */
         private Object readResolve() {
-            return new SphericalCoordinates(new Vector3D(x, y, z));
+            return new SphericalCoordinates(Vector3D.of(x, y, z));
         }
 
     }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
index 2445857..676a88b 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
@@ -42,6 +42,9 @@
  */
 public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterable<double[]> {
 
+    /** Message used for internal errors. */
+    private static final String INTERNAL_ERROR_MESSAGE = "Please file a bug report";
+
     /** Build an arcs set representing the whole circle.
      * @param tolerance tolerance below which close sub-arcs are merged together
      */
@@ -135,12 +138,12 @@ public ArcsSet(final Collection<SubHyperplane<S1Point>> boundary, final double t
         final double normalizedLower = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(lower);
         final double normalizedUpper = normalizedLower + (upper - lower);
         final SubHyperplane<S1Point> lowerCut =
-                new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane();
+                new LimitAngle(S1Point.of(normalizedLower), false, tolerance).wholeHyperplane();
 
         if (normalizedUpper <= Geometry.TWO_PI) {
             // simple arc starting after 0 and ending before 2 \pi
             final SubHyperplane<S1Point> upperCut =
-                    new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane();
+                    new LimitAngle(S1Point.of(normalizedUpper), true, tolerance).wholeHyperplane();
             return new BSPTree<>(lowerCut,
                                          new BSPTree<S1Point>(Boolean.FALSE),
                                          new BSPTree<>(upperCut,
@@ -151,7 +154,7 @@ public ArcsSet(final Collection<SubHyperplane<S1Point>> boundary, final double t
         } else {
             // arc wrapping around 2 \pi
             final SubHyperplane<S1Point> upperCut =
-                    new LimitAngle(new S1Point(normalizedUpper - Geometry.TWO_PI), true, tolerance).wholeHyperplane();
+                    new LimitAngle(S1Point.of(normalizedUpper - Geometry.TWO_PI), true, tolerance).wholeHyperplane();
             return new BSPTree<>(lowerCut,
                                          new BSPTree<>(upperCut,
                                                                new BSPTree<S1Point>(Boolean.FALSE),
@@ -459,7 +462,7 @@ protected void computeGeometricalProperties() {
             if (Precision.equals(size, Geometry.TWO_PI, 0)) {
                 setBarycenter(S1Point.NaN);
             } else if (size >= Precision.SAFE_MIN) {
-                setBarycenter(new S1Point(sum / (2 * size)));
+                setBarycenter(S1Point.of(sum / (2 * size)));
             } else {
                 final LimitAngle limit = (LimitAngle) getTree(false).getCut().getHyperplane();
                 setBarycenter(limit.getLocation());
@@ -495,9 +498,9 @@ protected void computeGeometricalProperties() {
                         final double previousOffset = alpha - previous;
                         final double currentOffset  = a[0] - alpha;
                         if (previousOffset < currentOffset) {
-                            return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                            return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                         } else {
-                            return new BoundaryProjection<>(point, new S1Point(a[0]), currentOffset);
+                            return new BoundaryProjection<>(point, S1Point.of(a[0]), currentOffset);
                         }
                     }
                 } else if (alpha <= a[1]) {
@@ -506,9 +509,9 @@ protected void computeGeometricalProperties() {
                     final double offset0 = a[0] - alpha;
                     final double offset1 = alpha - a[1];
                     if (offset0 < offset1) {
-                        return new BoundaryProjection<>(point, new S1Point(a[1]), offset1);
+                        return new BoundaryProjection<>(point, S1Point.of(a[1]), offset1);
                     } else {
-                        return new BoundaryProjection<>(point, new S1Point(a[0]), offset0);
+                        return new BoundaryProjection<>(point, S1Point.of(a[0]), offset0);
                     }
                 }
             }
@@ -529,18 +532,18 @@ protected void computeGeometricalProperties() {
                 final double previousOffset = alpha - (previous - Geometry.TWO_PI);
                 final double currentOffset  = first - alpha;
                 if (previousOffset < currentOffset) {
-                    return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                 } else {
-                    return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(first), currentOffset);
                 }
             } else {
                 // the test point is between last and 2\pi
                 final double previousOffset = alpha - previous;
                 final double currentOffset  = first + Geometry.TWO_PI - alpha;
                 if (previousOffset < currentOffset) {
-                    return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                 } else {
-                    return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(first), currentOffset);
                 }
             }
 
@@ -652,7 +655,7 @@ private void selectPending() {
                 }
                 if (end == null) {
                     // this should never happen
-                    throw new IllegalStateException("Please file a bug report");
+                    throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
                 }
 
                 // we have identified the last arc
@@ -787,11 +790,11 @@ public Split split(final Arc arc) {
      */
     private void addArcLimit(final BSPTree<S1Point> tree, final double alpha, final boolean isStart) {
 
-        final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance());
+        final LimitAngle limit = new LimitAngle(S1Point.of(alpha), !isStart, getTolerance());
         final BSPTree<S1Point> node = tree.getCell(limit.getLocation(), getTolerance());
         if (node.getCut() != null) {
             // this should never happen
-            throw new IllegalStateException("Please file a bug report");
+            throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
         }
 
         node.insertCut(limit);
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
index da4f85c..915e7a3 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
@@ -16,14 +16,18 @@
  */
 package org.apache.commons.geometry.spherical.oned;
 
+import java.io.Serializable;
+
 import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.internal.DoubleFunction1N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
 
 /** This class represents a point on the 1-sphere.
  * <p>Instances of this class are guaranteed to be immutable.</p>
  */
-public class S1Point implements Point<S1Point> {
+public final class S1Point implements Point<S1Point>, Serializable {
 
    // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
@@ -31,24 +35,24 @@
     // CHECKSTYLE: resume ConstantName
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 20131218L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction1N<S1Point> FACTORY = new DoubleFunction1N<S1Point>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public S1Point apply(double n) {
+            return S1Point.of(n);
+        }
+    };
 
-    /** Azimuthal angle \( \alpha \). */
+    /** Azimuthal angle in radians \( \alpha \). */
     private final double alpha;
 
     /** Corresponding 2D normalized vector. */
     private final Vector2D vector;
 
-    /** Simple constructor.
-     * Build a vector from its coordinates
-     * @param alpha azimuthal angle \( \alpha \)
-     * @see #getAlpha()
-     */
-    public S1Point(final double alpha) {
-        this(PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(alpha),
-             new Vector2D(Math.cos(alpha), Math.sin(alpha)));
-    }
-
     /** Build a point from its internal components.
      * @param alpha azimuthal angle \( \alpha \)
      * @param vector corresponding vector
@@ -58,7 +62,7 @@ private S1Point(final double alpha, final Vector2D vector) {
         this.vector = vector;
     }
 
-    /** Get the azimuthal angle \( \alpha \).
+    /** Get the azimuthal angle in radians \( \alpha \).
      * @return azimuthal angle \( \alpha \)
      * @see #S1Point(double)
      */
@@ -159,4 +163,32 @@ public int hashCode() {
         }
         return 1759 * Double.hashCode(alpha);
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleTupleFormat.getDefault().format(getAlpha());
+    }
+
+    /** Creates a new point instance from the given azimuthal coordinate value.
+     * @param alpha azimuthal angle in radians \( \alpha \)
+     * @return point instance with the given azimuth coordinate value
+     * @see #getAlpha()
+     */
+    public static S1Point of(double alpha) {
+        double normalizedAlpha = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(alpha);
+        Vector2D vector = Vector2D.of(Math.cos(normalizedAlpha), Math.sin(normalizedAlpha));
+
+        return new S1Point(normalizedAlpha, vector);
+    }
+
+    /** Parses the given string and returns a new point instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static S1Point parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java
new file mode 100644
index 0000000..020a968
--- /dev/null
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ *
+ * <p>
+ * Base package for spherical geometry components.
+ * </p>
+ */
+package org.apache.commons.geometry.spherical;
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
index 28d4e02..3b017ca 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.geometry.spherical.twod;
 
-import org.apache.commons.geometry.core.Point;
 import org.apache.commons.geometry.core.partitioning.Embedding;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.SubHyperplane;
@@ -147,7 +146,7 @@ public double getTolerance() {
      */
     @Override
     public S1Point toSubSpace(final S2Point point) {
-        return new S1Point(getPhase(point.getVector()));
+        return S1Point.of(getPhase(point.getVector()));
     }
 
     /** Get the phase angle of a direction.
@@ -169,7 +168,7 @@ public double getPhase(final Vector3D direction) {
      */
     @Override
     public S2Point toSpace(final S1Point point) {
-        return new S2Point(getPointAt(point.getAlpha()));
+        return S2Point.of(getPointAt(point.getAlpha()));
     }
 
     /** Get a circle point from its phase around the circle.
@@ -307,7 +306,7 @@ public boolean sameOrientationAs(final Hyperplane<S2Point> other) {
         /** {@inheritDoc} */
         @Override
         public S2Point apply(final S2Point point) {
-            return new S2Point(rotation.applyTo(point.getVector()));
+            return S2Point.of(rotation.applyTo(point.getVector()));
         }
 
         /** {@inheritDoc} */
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
index 997d051..609408a 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
@@ -148,7 +148,7 @@ void split(final Circle splitCircle,
             if (unwrappedEnd >= 0) {
                 // the start of the edge is inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + unwrappedEnd))),
+                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + unwrappedEnd))),
                                             unwrappedEnd, insideList, splitCircle);
                 alreadyManagedLength = unwrappedEnd;
             }
@@ -166,7 +166,7 @@ void split(final Circle splitCircle,
             } else {
                 // the edge is long enough to enter inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
                                             arcRelativeStart - alreadyManagedLength, outsideList, splitCircle);
                 alreadyManagedLength = arcRelativeStart;
 
@@ -177,7 +177,7 @@ void split(final Circle splitCircle,
                 } else {
                     // the edge is long enough to exit outside of the circle
                     previousVertex = addSubEdge(previousVertex,
-                                                new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                                new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
                                                 arcRelativeStart - alreadyManagedLength, insideList, splitCircle);
                     alreadyManagedLength = arcRelativeStart;
                     previousVertex = addSubEdge(previousVertex, end,
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
index 47b6d39..085f9c5 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
@@ -91,8 +91,8 @@ private void addContribution(final SubCircle sub, final boolean reversed,
         final Circle circle  = (Circle) sub.getHyperplane();
         final List<Arc> arcs = ((ArcsSet) sub.getRemainingRegion()).asList();
         for (final Arc a : arcs) {
-            final Vertex start = new Vertex(circle.toSpace(new S1Point(a.getInf())));
-            final Vertex end   = new Vertex(circle.toSpace(new S1Point(a.getSup())));
+            final Vertex start = new Vertex(circle.toSpace(S1Point.of(a.getInf())));
+            final Vertex end   = new Vertex(circle.toSpace(S1Point.of(a.getSup())));
             start.bindWith(circle);
             end.bindWith(circle);
             final Edge edge;
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
index 2cbcb03..258b17e 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
@@ -160,7 +160,7 @@ public S2Point getBarycenter() {
         if (summedBarycenter.getNormSq() == 0) {
             return S2Point.NaN;
         } else {
-            return new S2Point(summedBarycenter);
+            return S2Point.of(summedBarycenter);
         }
     }
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
index 4542e26..274552b 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
@@ -16,7 +16,11 @@
  */
 package org.apache.commons.geometry.spherical.twod;
 
+import java.io.Serializable;
+
 import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.internal.DoubleFunction2N;
+import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
 /** This class represents a point on the 2-sphere.
@@ -29,7 +33,7 @@
  * </p>
  * <p>Instances of this class are guaranteed to be immutable.</p>
  */
-public class S2Point implements Point<S2Point> {
+public final class S2Point implements Point<S2Point>, Serializable {
 
     /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */
     public static final S2Point PLUS_I = new S2Point(0, 0.5 * Math.PI, Vector3D.PLUS_X);
@@ -55,7 +59,17 @@
     // CHECKSTYLE: resume ConstantName
 
     /** Serializable UID. */
-    private static final long serialVersionUID = 20131218L;
+    private static final long serialVersionUID = 20180710L;
+
+    /** Factory for delegating instance creation. */
+    private static DoubleFunction2N<S2Point> FACTORY = new DoubleFunction2N<S2Point>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public S2Point apply(double n1, double n2) {
+            return S2Point.of(n1, n2);
+        }
+    };
 
     /** Azimuthal angle \( \theta \) in the x-y plane. */
     private final double theta;
@@ -66,29 +80,6 @@
     /** Corresponding 3D normalized vector. */
     private final Vector3D vector;
 
-    /** Simple constructor.
-     * Build a vector from its spherical coordinates
-     * @param theta azimuthal angle \( \theta \) in the x-y plane
-     * @param phi polar angle \( \varphi \)
-     * @see #getTheta()
-     * @see #getPhi()
-     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
-     */
-    public S2Point(final double theta, final double phi)
-        throws IllegalArgumentException {
-        this(theta, phi, vector(theta, phi));
-    }
-
-    /** Simple constructor.
-     * Build a vector from its underlying 3D vector
-     * @param vector 3D vector
-     * @exception IllegalArgumentException if vector norm is zero
-     */
-    public S2Point(final Vector3D vector) throws IllegalArgumentException {
-        this(Math.atan2(vector.getY(), vector.getX()), Vector3D.PLUS_Z.angle(vector),
-             vector.normalize());
-    }
-
     /** Build a point from its internal components.
      * @param theta azimuthal angle \( \theta \) in the x-y plane
      * @param phi polar angle \( \varphi \)
@@ -100,28 +91,6 @@ private S2Point(final double theta, final double phi, final Vector3D vector) {
         this.vector = vector;
     }
 
-    /** Build the normalized vector corresponding to spherical coordinates.
-     * @param theta azimuthal angle \( \theta \) in the x-y plane
-     * @param phi polar angle \( \varphi \)
-     * @return normalized vector
-     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
-     */
-    private static Vector3D vector(final double theta, final double phi)
-       throws IllegalArgumentException {
-
-        if (phi < 0 || phi > Math.PI) {
-            throw new IllegalArgumentException(phi + " is out of [" + 0 + ", " + Math.PI + "] range");
-        }
-
-        final double cosTheta = Math.cos(theta);
-        final double sinTheta = Math.sin(theta);
-        final double cosPhi   = Math.cos(phi);
-        final double sinPhi   = Math.sin(phi);
-
-        return new Vector3D(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi);
-
-    }
-
     /** Get the azimuthal angle \( \theta \) in the x-y plane.
      * @return azimuthal angle \( \theta \) in the x-y plane
      * @see #S2Point(double, double)
@@ -236,4 +205,64 @@ public int hashCode() {
         }
         return 134 * (37 * Double.hashCode(theta) +  Double.hashCode(phi));
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleTupleFormat.getDefault().format(getTheta(), getPhi());
+    }
+
+    /** Build a vector from its spherical coordinates
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @return point instance with the given coordinates
+     * @see #getTheta()
+     * @see #getPhi()
+     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    public static S2Point of(final double theta, final double phi) {
+        return new S2Point(theta, phi, vector(theta, phi));
+    }
+
+    /** Build a point from its underlying 3D vector
+     * @param vector 3D vector
+     * @return point instance with the coordinates determined by the given 3D vector
+     * @exception IllegalArgumentException if vector norm is zero
+     */
+    public static S2Point of(final Vector3D vector) {
+        return new S2Point(Math.atan2(vector.getY(), vector.getX()),
+                Vector3D.PLUS_Z.angle(vector),
+                vector.normalize());
+    }
+
+    /** Build the normalized vector corresponding to spherical coordinates.
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @return normalized vector
+     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    private static Vector3D vector(final double theta, final double phi)
+       throws IllegalArgumentException {
+
+        if (phi < 0 || phi > Math.PI) {
+            throw new IllegalArgumentException(phi + " is out of [" + 0 + ", " + Math.PI + "] range");
+        }
+
+        final double cosTheta = Math.cos(theta);
+        final double sinTheta = Math.sin(theta);
+        final double cosPhi   = Math.cos(phi);
+        final double sinPhi   = Math.sin(phi);
+
+        return Vector3D.of(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi);
+    }
+
+    /** Parses the given string and returns a new point instance. The expected string
+     * format is the same as that returned by {@link #toString()}.
+     * @param str the string to parse
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static S2Point parse(String str) throws IllegalArgumentException {
+        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
index fd22d6b..d9e3eeb 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
@@ -161,11 +161,11 @@ public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ...
         final S2Point[] array = new S2Point[n];
         final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian),
                                          outsideRadius, RotationConvention.VECTOR_OPERATOR);
-        array[0] = new S2Point(r0.applyTo(center));
+        array[0] = S2Point.of(r0.applyTo(center));
 
         final Rotation r = new Rotation(center, Geometry.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
         for (int i = 1; i < n; ++i) {
-            array[i] = new S2Point(r.applyTo(array[i - 1].getVector()));
+            array[i] = S2Point.of(r.applyTo(array[i - 1].getVector()));
         }
 
         return array;
@@ -325,7 +325,7 @@ protected void computeGeometricalProperties() throws IllegalStateException {
             if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
                 // the instance covers the whole space
                 setSize(4 * Math.PI);
-                setBarycenter(new S2Point(0, 0));
+                setBarycenter(S2Point.of(0, 0));
             } else {
                 setSize(0);
                 setBarycenter(S2Point.NaN);
@@ -478,13 +478,13 @@ protected void computeGeometricalProperties() throws IllegalStateException {
         if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(new S2Point(circle.getPole()).negate(),
+            return new EnclosingBall<>(S2Point.of(circle.getPole()).negate(),
                                                         0.5 * Math.PI);
         }
         if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(new S2Point(circle.getPole()),
+            return new EnclosingBall<>(S2Point.of(circle.getPole()),
                                                         0.5 * Math.PI);
         }
 
@@ -517,7 +517,7 @@ protected void computeGeometricalProperties() throws IllegalStateException {
             EnclosingBall<S2Point> enclosingS2 =
                     new EnclosingBall<>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
             for (Point3D outsidePoint : getOutsidePoints()) {
-                final S2Point outsideS2 = new S2Point(outsidePoint.asVector());
+                final S2Point outsideS2 = S2Point.of(outsidePoint.asVector());
                 final BoundaryProjection<S2Point> projection = projectToBoundary(outsideS2);
                 if (Math.PI - projection.getOffset() < enclosingS2.getRadius()) {
                     enclosingS2 = new EnclosingBall<>(outsideS2.negate(),
@@ -529,11 +529,11 @@ protected void computeGeometricalProperties() throws IllegalStateException {
         }
         final S2Point[] support = new S2Point[support3D.length];
         for (int i = 0; i < support3D.length; ++i) {
-            support[i] = new S2Point(support3D[i].asVector());
+            support[i] = S2Point.of(support3D[i].asVector());
         }
 
         final EnclosingBall<S2Point> enclosingS2 =
-                new EnclosingBall<>(new S2Point(enclosing3D.getCenter().asVector()),
+                new EnclosingBall<>(S2Point.of(enclosing3D.getCenter().asVector()),
                                                      Math.acos((1 + h * h - r * r) / (2 * h)),
                                                      support);
 
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalCoordinatesTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalCoordinatesTest.java
index 892814b..db3b76e 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalCoordinatesTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalCoordinatesTest.java
@@ -28,43 +28,43 @@
     public void testCoordinatesStoC() {
         double piO2 = 0.5 * Math.PI;
         SphericalCoordinates sc1 = new SphericalCoordinates(2.0, 0, piO2);
-        Assert.assertEquals(0, sc1.getCartesian().distance(new Vector3D(2, 0, 0)), 1.0e-10);
+        Assert.assertEquals(0, sc1.getCartesian().distance(Vector3D.of(2, 0, 0)), 1.0e-10);
         SphericalCoordinates sc2 = new SphericalCoordinates(2.0, piO2, piO2);
-        Assert.assertEquals(0, sc2.getCartesian().distance(new Vector3D(0, 2, 0)), 1.0e-10);
+        Assert.assertEquals(0, sc2.getCartesian().distance(Vector3D.of(0, 2, 0)), 1.0e-10);
         SphericalCoordinates sc3 = new SphericalCoordinates(2.0, Math.PI, piO2);
-        Assert.assertEquals(0, sc3.getCartesian().distance(new Vector3D(-2, 0, 0)), 1.0e-10);
+        Assert.assertEquals(0, sc3.getCartesian().distance(Vector3D.of(-2, 0, 0)), 1.0e-10);
         SphericalCoordinates sc4 = new SphericalCoordinates(2.0, -piO2, piO2);
-        Assert.assertEquals(0, sc4.getCartesian().distance(new Vector3D(0, -2, 0)), 1.0e-10);
+        Assert.assertEquals(0, sc4.getCartesian().distance(Vector3D.of(0, -2, 0)), 1.0e-10);
         SphericalCoordinates sc5 = new SphericalCoordinates(2.0, 1.23456, 0);
-        Assert.assertEquals(0, sc5.getCartesian().distance(new Vector3D(0, 0, 2)), 1.0e-10);
+        Assert.assertEquals(0, sc5.getCartesian().distance(Vector3D.of(0, 0, 2)), 1.0e-10);
         SphericalCoordinates sc6 = new SphericalCoordinates(2.0, 6.54321, Math.PI);
-        Assert.assertEquals(0, sc6.getCartesian().distance(new Vector3D(0, 0, -2)), 1.0e-10);
+        Assert.assertEquals(0, sc6.getCartesian().distance(Vector3D.of(0, 0, -2)), 1.0e-10);
     }
 
     @Test
     public void testCoordinatesCtoS() {
         double piO2 = 0.5 * Math.PI;
-        SphericalCoordinates sc1 = new SphericalCoordinates(new Vector3D(2, 0, 0));
+        SphericalCoordinates sc1 = new SphericalCoordinates(Vector3D.of(2, 0, 0));
         Assert.assertEquals(2,           sc1.getR(),     1.0e-10);
         Assert.assertEquals(0,           sc1.getTheta(), 1.0e-10);
         Assert.assertEquals(piO2,        sc1.getPhi(),   1.0e-10);
-        SphericalCoordinates sc2 = new SphericalCoordinates(new Vector3D(0, 2, 0));
+        SphericalCoordinates sc2 = new SphericalCoordinates(Vector3D.of(0, 2, 0));
         Assert.assertEquals(2,           sc2.getR(),     1.0e-10);
         Assert.assertEquals(piO2,        sc2.getTheta(), 1.0e-10);
         Assert.assertEquals(piO2,        sc2.getPhi(),   1.0e-10);
-        SphericalCoordinates sc3 = new SphericalCoordinates(new Vector3D(-2, 0, 0));
+        SphericalCoordinates sc3 = new SphericalCoordinates(Vector3D.of(-2, 0, 0));
         Assert.assertEquals(2,           sc3.getR(),     1.0e-10);
         Assert.assertEquals(Math.PI, sc3.getTheta(), 1.0e-10);
         Assert.assertEquals(piO2,        sc3.getPhi(),   1.0e-10);
-        SphericalCoordinates sc4 = new SphericalCoordinates(new Vector3D(0, -2, 0));
+        SphericalCoordinates sc4 = new SphericalCoordinates(Vector3D.of(0, -2, 0));
         Assert.assertEquals(2,           sc4.getR(),     1.0e-10);
         Assert.assertEquals(-piO2,       sc4.getTheta(), 1.0e-10);
         Assert.assertEquals(piO2,        sc4.getPhi(),   1.0e-10);
-        SphericalCoordinates sc5 = new SphericalCoordinates(new Vector3D(0, 0, 2));
+        SphericalCoordinates sc5 = new SphericalCoordinates(Vector3D.of(0, 0, 2));
         Assert.assertEquals(2,           sc5.getR(),     1.0e-10);
         //  don't check theta on poles, as it is singular
         Assert.assertEquals(0,           sc5.getPhi(),   1.0e-10);
-        SphericalCoordinates sc6 = new SphericalCoordinates(new Vector3D(0, 0, -2));
+        SphericalCoordinates sc6 = new SphericalCoordinates(Vector3D.of(0, 0, -2));
         Assert.assertEquals(2,           sc6.getR(),     1.0e-10);
         //  don't check theta on poles, as it is singular
         Assert.assertEquals(Math.PI, sc6.getPhi(),   1.0e-10);
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
index 313e455..a43bc3d 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
@@ -89,7 +89,7 @@ public static ArcsSet parseArcsSet(final String s)
             @Override
             protected LimitAngle parseHyperplane()
                 throws IOException, ParseException {
-                return new LimitAngle(new S1Point(getNumber()), getBoolean(), getNumber());
+                return new LimitAngle(S1Point.of(getNumber()), getBoolean(), getNumber());
             }
 
         };
@@ -110,7 +110,7 @@ public static SphericalPolygonsSet parseSphericalPolygonsSet(final String s)
             @Override
             public Circle parseHyperplane()
                 throws IOException, ParseException {
-                return new Circle(new Vector3D(getNumber(), getNumber(), getNumber()), getNumber());
+                return new Circle(Vector3D.of(getNumber(), getNumber(), getNumber()), getNumber());
             }
 
         };
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
index 3b59551..a6bab1a 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
@@ -39,12 +39,12 @@ public void testArc() {
         ArcsSet set = new ArcsSet(2.3, 5.7, 1.0e-10);
         Assert.assertEquals(3.4, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(2.3)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.7)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(8.5)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(8.7)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(2.3)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.7)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(8.5)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(8.7)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(3.0)));
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(2.3, set.asList().get(0).getInf(), 1.0e-10);
         Assert.assertEquals(5.7, set.asList().get(0).getSup(), 1.0e-10);
@@ -55,12 +55,12 @@ public void testWrapAround2PiArc() {
         ArcsSet set = new ArcsSet(5.7 - Geometry.TWO_PI, 2.3, 1.0e-10);
         Assert.assertEquals(Geometry.TWO_PI - 3.4, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(2.3)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.7)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(8.5)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(8.7)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(2.3)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.7)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(8.5)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(8.7)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(3.0)));
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(5.7, set.asList().get(0).getInf(), 1.0e-10);
         Assert.assertEquals(2.3 + Geometry.TWO_PI, set.asList().get(0).getSup(), 1.0e-10);
@@ -72,7 +72,7 @@ public void testSplitOver2Pi() {
         Arc     arc = new Arc(1.5 * Math.PI, 2.5 * Math.PI, 1.0e-10);
         ArcsSet.Split split = set.split(arc);
         for (double alpha = 0.0; alpha <= Geometry.TWO_PI; alpha += 0.01) {
-            S1Point p = new S1Point(alpha);
+            S1Point p = S1Point.of(alpha);
             if (alpha < 0.5 * Math.PI || alpha > 1.5 * Math.PI) {
                 Assert.assertEquals(Location.OUTSIDE, split.getPlus().checkPoint(p));
                 Assert.assertEquals(Location.INSIDE,  split.getMinus().checkPoint(p));
@@ -89,7 +89,7 @@ public void testSplitAtEnd() {
         Arc     arc = new Arc(Math.PI, Geometry.TWO_PI, 1.0e-10);
         ArcsSet.Split split = set.split(arc);
         for (double alpha = 0.01; alpha < Geometry.TWO_PI; alpha += 0.01) {
-            S1Point p = new S1Point(alpha);
+            S1Point p = S1Point.of(alpha);
             if (alpha > Math.PI) {
                 Assert.assertEquals(Location.OUTSIDE, split.getPlus().checkPoint(p));
                 Assert.assertEquals(Location.INSIDE,  split.getMinus().checkPoint(p));
@@ -99,11 +99,11 @@ public void testSplitAtEnd() {
             }
         }
 
-        S1Point zero = new S1Point(0.0);
+        S1Point zero = S1Point.of(0.0);
         Assert.assertEquals(Location.BOUNDARY,  split.getPlus().checkPoint(zero));
         Assert.assertEquals(Location.BOUNDARY,  split.getMinus().checkPoint(zero));
 
-        S1Point pi = new S1Point(Math.PI);
+        S1Point pi = S1Point.of(Math.PI);
         Assert.assertEquals(Location.BOUNDARY,  split.getPlus().checkPoint(pi));
         Assert.assertEquals(Location.BOUNDARY,  split.getMinus().checkPoint(pi));
 
@@ -118,9 +118,9 @@ public void testWrongInterval() {
     public void testFullEqualEndPoints() {
         ArcsSet set = new ArcsSet(1.0, 1.0, 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(9.0)));
+        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(9.0)));
         for (double alpha = -20.0; alpha <= 20.0; alpha += 0.1) {
-            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(alpha)));
+            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(alpha)));
         }
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(0.0, set.asList().get(0).getInf(), 1.0e-10);
@@ -132,9 +132,9 @@ public void testFullEqualEndPoints() {
     public void testFullCircle() {
         ArcsSet set = new ArcsSet(1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(9.0)));
+        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(9.0)));
         for (double alpha = -20.0; alpha <= 20.0; alpha += 0.1) {
-            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(alpha)));
+            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(alpha)));
         }
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(0.0, set.asList().get(0).getInf(), 1.0e-10);
@@ -163,8 +163,8 @@ public void testTiny() {
     @Test
     public void testSpecialConstruction() {
         List<SubHyperplane<S1Point>> boundary = new ArrayList<>();
-        boundary.add(new LimitAngle(new S1Point(0.0), false, 1.0e-10).wholeHyperplane());
-        boundary.add(new LimitAngle(new S1Point(Geometry.TWO_PI - 1.0e-11), true, 1.0e-10).wholeHyperplane());
+        boundary.add(new LimitAngle(S1Point.of(0.0), false, 1.0e-10).wholeHyperplane());
+        boundary.add(new LimitAngle(S1Point.of(Geometry.TWO_PI - 1.0e-11), true, 1.0e-10).wholeHyperplane());
         ArcsSet set = new ArcsSet(boundary, 1.0e-10);
         Assert.assertEquals(Geometry.TWO_PI, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
@@ -190,20 +190,20 @@ public void testDifference() {
 
         ArcsSet aMb = (ArcsSet) new RegionFactory<S1Point>().difference(a, b);
         for (int k = -2; k < 3; ++k) {
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(1.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(1.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(2.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(3.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(3.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(4.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(6.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.2 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(1.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(1.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(2.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(3.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(3.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(4.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(6.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.2 + k * Geometry.TWO_PI)));
         }
 
         List<Arc> aMbList = aMb.asList();
@@ -236,19 +236,19 @@ public void testIntersection() {
 
         ArcsSet aMb = (ArcsSet) new RegionFactory<S1Point>().intersection(a, b);
         for (int k = -2; k < 3; ++k) {
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(1.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(1.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(2.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(3.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(3.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(4.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.4 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.5 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(5.6 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.2 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(1.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(1.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(2.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(3.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(3.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(4.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.4 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.5 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(5.6 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.2 + k * Geometry.TWO_PI)));
         }
 
         List<Arc> aMbList = aMb.asList();
@@ -270,15 +270,15 @@ public void testMultiple() {
                                                               new ArcsSet(0.5, 2.0, 1.0e-10)),
                                                               new ArcsSet(0.0, 5.5, 1.0e-10));
         Assert.assertEquals(3.0, set.getSize(), 1.0e-10);
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(0.0)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(4.0)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(6.0)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(5.25)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(0.5)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(3.0)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.0)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.5)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(0.0)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(4.0)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(6.0)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(5.25)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(0.5)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.5)));
 
         List<Arc> list = set.asList();
         Assert.assertEquals(2, list.size());
@@ -337,8 +337,8 @@ public void testEmptyTree() {
     @Test
     public void testShiftedAngles() {
         for (int k = -2; k < 3; ++k) {
-            SubLimitAngle l1  = new LimitAngle(new S1Point(1.0 + k * Geometry.TWO_PI), false, 1.0e-10).wholeHyperplane();
-            SubLimitAngle l2  = new LimitAngle(new S1Point(1.5 + k * Geometry.TWO_PI), true,  1.0e-10).wholeHyperplane();
+            SubLimitAngle l1  = new LimitAngle(S1Point.of(1.0 + k * Geometry.TWO_PI), false, 1.0e-10).wholeHyperplane();
+            SubLimitAngle l2  = new LimitAngle(S1Point.of(1.5 + k * Geometry.TWO_PI), true,  1.0e-10).wholeHyperplane();
             ArcsSet set = new ArcsSet(new BSPTree<>(l1,
                                                             new BSPTree<S1Point>(Boolean.FALSE),
                                                             new BSPTree<>(l2,
@@ -349,9 +349,9 @@ public void testShiftedAngles() {
                                       1.0e-10);
             for (double alpha = 1.0e-6; alpha < Geometry.TWO_PI; alpha += 0.001) {
                 if (alpha < 1 || alpha > 1.5) {
-                    Assert.assertEquals(Location.OUTSIDE, set.checkPoint(new S1Point(alpha)));
+                    Assert.assertEquals(Location.OUTSIDE, set.checkPoint(S1Point.of(alpha)));
                 } else {
-                    Assert.assertEquals(Location.INSIDE,  set.checkPoint(new S1Point(alpha)));
+                    Assert.assertEquals(Location.INSIDE,  set.checkPoint(S1Point.of(alpha)));
                 }
             }
         }
@@ -360,9 +360,9 @@ public void testShiftedAngles() {
 
     @Test(expected=ArcsSet.InconsistentStateAt2PiWrapping.class)
     public void testInconsistentState() {
-        SubLimitAngle l1 = new LimitAngle(new S1Point(1.0), false, 1.0e-10).wholeHyperplane();
-        SubLimitAngle l2 = new LimitAngle(new S1Point(2.0), true,  1.0e-10).wholeHyperplane();
-        SubLimitAngle l3 = new LimitAngle(new S1Point(3.0), false, 1.0e-10).wholeHyperplane();
+        SubLimitAngle l1 = new LimitAngle(S1Point.of(1.0), false, 1.0e-10).wholeHyperplane();
+        SubLimitAngle l2 = new LimitAngle(S1Point.of(2.0), true,  1.0e-10).wholeHyperplane();
+        SubLimitAngle l3 = new LimitAngle(S1Point.of(3.0), false, 1.0e-10).wholeHyperplane();
         new ArcsSet(new BSPTree<>(l1,
                                           new BSPTree<S1Point>(Boolean.FALSE),
                                           new BSPTree<>(l2,
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
index 454979a..f9a9b3b 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
@@ -25,7 +25,7 @@
     @Test
     public void testReversedLimit() {
         for (int k = -2; k < 3; ++k) {
-            LimitAngle l  = new LimitAngle(new S1Point(1.0 + k * Geometry.TWO_PI), false, 1.0e-10);
+            LimitAngle l  = new LimitAngle(S1Point.of(1.0 + k * Geometry.TWO_PI), false, 1.0e-10);
             Assert.assertEquals(l.getLocation().getAlpha(), l.getReverse().getLocation().getAlpha(), 1.0e-10);
             Assert.assertEquals(l.getTolerance(), l.getReverse().getTolerance(), 1.0e-10);
             Assert.assertTrue(l.sameOrientationAs(l));
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
index 8b0b989..ba81963 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
@@ -22,12 +22,14 @@
 
 public class S1PointTest {
 
+    private static final double EPS = 1e-10;
+
     @Test
     public void testS1Point() {
         for (int k = -2; k < 3; ++k) {
-            S1Point p = new S1Point(1.0 + k * Geometry.TWO_PI);
-            Assert.assertEquals(Math.cos(1.0), p.getVector().getX(), 1.0e-10);
-            Assert.assertEquals(Math.sin(1.0), p.getVector().getY(), 1.0e-10);
+            S1Point p = S1Point.of(1.0 + k * Geometry.TWO_PI);
+            Assert.assertEquals(Math.cos(1.0), p.getVector().getX(), EPS);
+            Assert.assertEquals(Math.sin(1.0), p.getVector().getY(), EPS);
             Assert.assertFalse(p.isNaN());
         }
     }
@@ -35,14 +37,14 @@ public void testS1Point() {
     @Test
     public void testNaN() {
         Assert.assertTrue(S1Point.NaN.isNaN());
-        Assert.assertTrue(S1Point.NaN.equals(new S1Point(Double.NaN)));
-        Assert.assertFalse(new S1Point(1.0).equals(S1Point.NaN));
+        Assert.assertTrue(S1Point.NaN.equals(S1Point.of(Double.NaN)));
+        Assert.assertFalse(S1Point.of(1.0).equals(S1Point.NaN));
     }
 
     @Test
     public void testEquals() {
-        S1Point a = new S1Point(1.0);
-        S1Point b = new S1Point(1.0);
+        S1Point a = S1Point.of(1.0);
+        S1Point b = S1Point.of(1.0);
         Assert.assertEquals(a.hashCode(), b.hashCode());
         Assert.assertFalse(a == b);
         Assert.assertTrue(a.equals(b));
@@ -52,9 +54,33 @@ public void testEquals() {
 
     @Test
     public void testDistance() {
-        S1Point a = new S1Point(1.0);
-        S1Point b = new S1Point(a.getAlpha() + 0.5 * Math.PI);
+        S1Point a = S1Point.of(1.0);
+        S1Point b = S1Point.of(a.getAlpha() + 0.5 * Math.PI);
         Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10);
     }
 
+    @Test
+    public void testToString() {
+        // act/assert
+        Assert.assertEquals("(0.0)", S1Point.of(0.0).toString());
+        Assert.assertEquals("(1.0)", S1Point.of(1.0).toString());
+    }
+
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(S1Point.parse("(0)"), 0.0);
+        checkPoint(S1Point.parse("(1)"), 1.0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        S1Point.parse("abc");
+    }
+
+    private void checkPoint(S1Point p, double alpha) {
+        Assert.assertEquals(alpha, p.getAlpha(), EPS);
+    }
+
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
index 7458ee9..7b9ad53 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
@@ -36,7 +36,7 @@
 
     @Test
     public void testEquator() {
-        Circle circle = new Circle(new Vector3D(0, 0, 1000), 1.0e-10).copySelf();
+        Circle circle = new Circle(Vector3D.of(0, 0, 1000), 1.0e-10).copySelf();
         Assert.assertEquals(Vector3D.PLUS_Z, circle.getPole());
         Assert.assertEquals(1.0e-10, circle.getTolerance(), 1.0e-20);
         circle.revertSelf();
@@ -47,7 +47,7 @@ public void testEquator() {
 
     @Test
     public void testXY() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0).distance(circle.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0.5 * Math.PI).distance(circle.getYAxis()), 1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getXAxis(), circle.getYAxis()), 1.0e-10);
@@ -60,7 +60,7 @@ public void testXY() {
 
     @Test
     public void testReverse() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Circle reversed = circle.getReverse();
         Assert.assertEquals(0.0, reversed.getPointAt(0).distance(reversed.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, reversed.getPointAt(0.5 * Math.PI).distance(reversed.getYAxis()), 1.0e-10);
@@ -82,8 +82,8 @@ public void testReverse() {
 
     @Test
     public void testPhase() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
-        Vector3D p = new Vector3D(1, 2, -4);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
+        Vector3D p = Vector3D.of(1, 2, -4);
         Vector3D samePhase = circle.getPointAt(circle.getPhase(p));
         Assert.assertEquals(0.0,
                             Vector3D.angle(Vector3D.crossProduct(circle.getPole(), p),
@@ -98,20 +98,20 @@ public void testPhase() {
 
     @Test
     public void testSubSpace() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
-        Assert.assertEquals(0.0, circle.toSubSpace(new S2Point(circle.getXAxis())).getAlpha(), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(new S2Point(circle.getYAxis())).getAlpha(), 1.0e-10);
-        Vector3D p = new Vector3D(1, 2, -4);
-        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(new S2Point(p)).getAlpha(), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
+        Assert.assertEquals(0.0, circle.toSubSpace(S2Point.of(circle.getXAxis())).getAlpha(), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.of(circle.getYAxis())).getAlpha(), 1.0e-10);
+        Vector3D p = Vector3D.of(1, 2, -4);
+        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.of(p)).getAlpha(), 1.0e-10);
     }
 
     @Test
     public void testSpace() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         for (double alpha = 0; alpha < Geometry.TWO_PI; alpha += 0.1) {
             Vector3D p = Vector3D.linearCombination(Math.cos(alpha), circle.getXAxis(),
                                       Math.sin(alpha), circle.getYAxis());
-            Vector3D q = circle.toSpace(new S1Point(alpha)).getVector();
+            Vector3D q = circle.toSpace(S1Point.of(alpha)).getVector();
             Assert.assertEquals(0.0, p.distance(q), 1.0e-10);
             Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getPole(), q), 1.0e-10);
         }
@@ -120,12 +120,12 @@ public void testSpace() {
     @Test
     public void testOffset() {
         Circle circle = new Circle(Vector3D.PLUS_Z, 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.PLUS_X)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.MINUS_X)), 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.PLUS_Y)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.MINUS_Y)), 1.0e-10);
-        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(new S2Point(Vector3D.PLUS_Z)),  1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(new S2Point(Vector3D.MINUS_Z)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_X)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_X)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_Y)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_Y)), 1.0e-10);
+        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.PLUS_Z)),  1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.MINUS_Z)), 1.0e-10);
 
     }
 
@@ -164,7 +164,7 @@ public void testTransform() {
                                       RotationConvention.VECTOR_OPERATOR);
             Transform<S2Point, S1Point> t = Circle.getTransform(r);
 
-            S2Point  p = new S2Point(Vector3D.of(sphRandom.nextVector()));
+            S2Point  p = S2Point.of(Vector3D.of(sphRandom.nextVector()));
             S2Point tp = t.apply(p);
             Assert.assertEquals(0.0, r.applyTo(p.getVector()).distance(tp.getVector()), 1.0e-10);
 
@@ -175,7 +175,7 @@ public void testTransform() {
             Assert.assertEquals(0.0, r.applyTo(c.getYAxis()).distance(tc.getYAxis()), 1.0e-10);
             Assert.assertEquals(c.getTolerance(), ((Circle) t.apply(c)).getTolerance(), 1.0e-10);
 
-            SubLimitAngle  sub = new LimitAngle(new S1Point(Geometry.TWO_PI * random.nextDouble()),
+            SubLimitAngle  sub = new LimitAngle(S1Point.of(Geometry.TWO_PI * random.nextDouble()),
                                                 random.nextBoolean(), 1.0e-10).wholeHyperplane();
             Vector3D psub = c.getPointAt(((LimitAngle) sub.getHyperplane()).getLocation().getAlpha());
             SubLimitAngle tsub = (SubLimitAngle) t.apply(sub, c, tc);
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
index bbf5891..2e15d09 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
@@ -23,40 +23,42 @@
 
 public class S2PointTest {
 
+    private static final double EPS = 1e-10;
+
     @Test
     public void testS2Point() {
         for (int k = -2; k < 3; ++k) {
-            S2Point p = new S2Point(1.0 + k * Geometry.TWO_PI, 1.4);
-            Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getTheta(), 1.0e-10);
-            Assert.assertEquals(1.4, p.getPhi(), 1.0e-10);
-            Assert.assertEquals(Math.cos(1.0) * Math.sin(1.4), p.getVector().getX(), 1.0e-10);
-            Assert.assertEquals(Math.sin(1.0) * Math.sin(1.4), p.getVector().getY(), 1.0e-10);
-            Assert.assertEquals(Math.cos(1.4), p.getVector().getZ(), 1.0e-10);
+            S2Point p = S2Point.of(1.0 + k * Geometry.TWO_PI, 1.4);
+            Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getTheta(), EPS);
+            Assert.assertEquals(1.4, p.getPhi(), EPS);
+            Assert.assertEquals(Math.cos(1.0) * Math.sin(1.4), p.getVector().getX(), EPS);
+            Assert.assertEquals(Math.sin(1.0) * Math.sin(1.4), p.getVector().getY(), EPS);
+            Assert.assertEquals(Math.cos(1.4), p.getVector().getZ(), EPS);
             Assert.assertFalse(p.isNaN());
         }
     }
 
     @Test(expected=IllegalArgumentException.class)
     public void testNegativePolarAngle() {
-        new S2Point(1.0, -1.0);
+        S2Point.of(1.0, -1.0);
     }
 
     @Test(expected=IllegalArgumentException.class)
     public void testTooLargePolarAngle() {
-        new S2Point(1.0, 3.5);
+        S2Point.of(1.0, 3.5);
     }
 
     @Test
     public void testNaN() {
         Assert.assertTrue(S2Point.NaN.isNaN());
-        Assert.assertTrue(S2Point.NaN.equals(new S2Point(Double.NaN, 1.0)));
-        Assert.assertFalse(new S2Point(1.0, 1.3).equals(S2Point.NaN));
+        Assert.assertTrue(S2Point.NaN.equals(S2Point.of(Double.NaN, 1.0)));
+        Assert.assertFalse(S2Point.of(1.0, 1.3).equals(S2Point.NaN));
     }
 
     @Test
     public void testEquals() {
-        S2Point a = new S2Point(1.0, 1.0);
-        S2Point b = new S2Point(1.0, 1.0);
+        S2Point a = S2Point.of(1.0, 1.0);
+        S2Point b = S2Point.of(1.0, 1.0);
         Assert.assertEquals(a.hashCode(), b.hashCode());
         Assert.assertFalse(a == b);
         Assert.assertTrue(a.equals(b));
@@ -66,12 +68,36 @@ public void testEquals() {
 
     @Test
     public void testDistance() {
-        S2Point a = new S2Point(1.0, 0.5 * Math.PI);
-        S2Point b = new S2Point(a.getTheta() + 0.5 * Math.PI, a.getPhi());
+        S2Point a = S2Point.of(1.0, 0.5 * Math.PI);
+        S2Point b = S2Point.of(a.getTheta() + 0.5 * Math.PI, a.getPhi());
         Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10);
         Assert.assertEquals(Math.PI, a.distance(a.negate()), 1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, S2Point.MINUS_I.distance(S2Point.MINUS_K), 1.0e-10);
-        Assert.assertEquals(0.0, new S2Point(1.0, 0).distance(new S2Point(2.0, 0)), 1.0e-10);
+        Assert.assertEquals(0.0, S2Point.of(1.0, 0).distance(S2Point.of(2.0, 0)), 1.0e-10);
+    }
+
+    @Test
+    public void testToString() {
+        // act/assert
+        Assert.assertEquals("(0.0, 0.0)", S2Point.of(0.0, 0.0).toString());
+        Assert.assertEquals("(1.0, 2.0)", S2Point.of(1.0, 2.0).toString());
+    }
+
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(S2Point.parse("(0,0)"), 0.0, 0.0);
+        checkPoint(S2Point.parse("(1,2)"), 1.0, 2.0);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        S2Point.parse("abc");
+    }
+
+    private void checkPoint(S2Point p, double theta, double phi) {
+        Assert.assertEquals(theta, p.getTheta(), EPS);
+        Assert.assertEquals(phi, p.getPhi(), EPS);
+    }
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
index 0152188..0f056e8 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
@@ -43,7 +43,7 @@ public void testFullSphere() {
                                                              0x852fd2a0ed8d2f6dl));
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.INSIDE, full.checkPoint(new S2Point(v)));
+            Assert.assertEquals(Location.INSIDE, full.checkPoint(S2Point.of(v)));
         }
         Assert.assertEquals(4 * Math.PI, new SphericalPolygonsSet(0.01, new S2Point[0]).getSize(), 1.0e-10);
         Assert.assertEquals(0, new SphericalPolygonsSet(0.01, new S2Point[0]).getBoundarySize(), 1.0e-10);
@@ -61,7 +61,7 @@ public void testEmpty() {
                                                              0x76d9205d6167b6ddl));
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(new S2Point(v)));
+            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(S2Point.of(v)));
         }
         Assert.assertEquals(0, empty.getSize(), 1.0e-10);
         Assert.assertEquals(0, empty.getBoundarySize(), 1.0e-10);
@@ -81,11 +81,11 @@ public void testSouthHemisphere() {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if (v.getZ() < -sinTol) {
-                Assert.assertEquals(Location.INSIDE, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, south.checkPoint(S2Point.of(v)));
             } else if (v.getZ() > sinTol) {
-                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(S2Point.of(v)));
             }
         }
         Assert.assertEquals(1, south.getBoundaryLoops().size());
@@ -117,11 +117,11 @@ public void testPositiveOctantByIntersection() {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -156,7 +156,7 @@ public void testPositiveOctantByIntersection() {
         Assert.assertEquals(3, count);
 
         Assert.assertEquals(0.0,
-                            octant.getBarycenter().distance(new S2Point(new Vector3D(1, 1, 1))),
+                            octant.getBarycenter().distance(S2Point.of(Vector3D.of(1, 1, 1))),
                             1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, octant.getSize(), 1.0e-10);
 
@@ -166,7 +166,7 @@ public void testPositiveOctantByIntersection() {
 
         EnclosingBall<S2Point> reversedCap =
                 ((SphericalPolygonsSet) factory.getComplement(octant)).getEnclosingCap();
-        Assert.assertEquals(0, reversedCap.getCenter().distance(new S2Point(new Vector3D(-1, -1, -1))), 1.0e-10);
+        Assert.assertEquals(0, reversedCap.getCenter().distance(S2Point.of(Vector3D.of(-1, -1, -1))), 1.0e-10);
         Assert.assertEquals(Math.PI - Math.asin(1.0 / Math.sqrt(3)), reversedCap.getRadius(), 1.0e-10);
 
     }
@@ -182,11 +182,11 @@ public void testPositiveOctantByVertices() {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
             }
         }
     }
@@ -208,11 +208,11 @@ public void testNonConvex() {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if (((v.getX() < -sinTol) || (v.getY() < -sinTol)) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(S2Point.of(v)));
             } else if (((v.getX() > sinTol) && (v.getY() > sinTol)) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -274,14 +274,14 @@ public void testModeratlyComplexShape() {
         boundary.add(create(Vector3D.PLUS_Z,  Vector3D.MINUS_Y, Vector3D.PLUS_X,  tol, 0.0, 0.5 * Math.PI));
         SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol);
 
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1, -1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D( 1, -1,  1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1,  1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1,  1, -1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(new Vector3D(-1, -1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1,  1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1, -1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1, -1).normalize())));
 
         Assert.assertEquals(Geometry.TWO_PI, polygon.getSize(), 1.0e-10);
         Assert.assertEquals(3 * Math.PI, polygon.getBoundarySize(), 1.0e-10);
@@ -342,15 +342,15 @@ public void testSeveralParts() {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() < -sinTol) && (v.getY() < -sinTol) && (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < sinTol) && (v.getY() < sinTol) && (v.getZ() < sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() > -sinTol) && (v.getY() > -sinTol) && (v.getZ() > -sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -366,17 +366,17 @@ public void testSeveralParts() {
     public void testPartWithHole() {
         double tol = 0.01;
         double alpha = 0.7;
-        S2Point center = new S2Point(new Vector3D(1, 1, 1));
+        S2Point center = S2Point.of(Vector3D.of(1, 1, 1));
         SphericalPolygonsSet hexa = new SphericalPolygonsSet(center.getVector(), Vector3D.PLUS_Z, alpha, 6, tol);
         SphericalPolygonsSet hole  = new SphericalPolygonsSet(tol,
-                                                              new S2Point(Math.PI / 6, Math.PI / 3),
-                                                              new S2Point(Math.PI / 3, Math.PI / 3),
-                                                              new S2Point(Math.PI / 4, Math.PI / 6));
+                                                              S2Point.of(Math.PI / 6, Math.PI / 3),
+                                                              S2Point.of(Math.PI / 3, Math.PI / 3),
+                                                              S2Point.of(Math.PI / 4, Math.PI / 6));
         SphericalPolygonsSet hexaWithHole =
                 (SphericalPolygonsSet) new RegionFactory<S2Point>().difference(hexa, hole);
 
         for (double phi = center.getPhi() - alpha + 0.1; phi < center.getPhi() + alpha - 0.1; phi += 0.07) {
-            Location l = hexaWithHole.checkPoint(new S2Point(Math.PI / 4, phi));
+            Location l = hexaWithHole.checkPoint(S2Point.of(Math.PI / 4, phi));
             if (phi < Math.PI / 6 || phi > Math.PI / 3) {
                 Assert.assertEquals(Location.INSIDE,  l);
             } else {
@@ -395,7 +395,7 @@ public void testPartWithHole() {
     @Test
     public void testConcentricSubParts() {
         double tol = 0.001;
-        Vector3D center = new Vector3D(1, 1, 1);
+        Vector3D center = Vector3D.of(1, 1, 1);
         SphericalPolygonsSet hexaOut   = new SphericalPolygonsSet(center, Vector3D.PLUS_Z, 0.9,  6, tol);
         SphericalPolygonsSet hexaIn    = new SphericalPolygonsSet(center, Vector3D.PLUS_Z, 0.8,  6, tol);
         SphericalPolygonsSet pentaOut  = new SphericalPolygonsSet(center, Vector3D.PLUS_Z, 0.7,  5, tol);
@@ -428,24 +428,24 @@ public void testConcentricSubParts() {
                             concentric.getSize(), 1.0e-10);
 
         // we expect lots of sign changes as we traverse all concentric rings
-        double phi = new S2Point(center).getPhi();
-        Assert.assertEquals(+0.207, concentric.projectToBoundary(new S2Point(-0.60,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.048, concentric.projectToBoundary(new S2Point(-0.21,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point(-0.10,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 0.01,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.049, concentric.projectToBoundary(new S2Point( 0.16,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.038, concentric.projectToBoundary(new S2Point( 0.29,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.097, concentric.projectToBoundary(new S2Point( 0.48,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.64,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.072, concentric.projectToBoundary(new S2Point( 0.79,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.93,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.091, concentric.projectToBoundary(new S2Point( 1.08,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.037, concentric.projectToBoundary(new S2Point( 1.28,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.051, concentric.projectToBoundary(new S2Point( 1.40,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 1.55,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point( 1.67,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.044, concentric.projectToBoundary(new S2Point( 1.79,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.201, concentric.projectToBoundary(new S2Point( 2.16,  phi)).getOffset(), 0.01);
+        double phi = S2Point.of(center).getPhi();
+        Assert.assertEquals(+0.207, concentric.projectToBoundary(S2Point.of(-0.60,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.048, concentric.projectToBoundary(S2Point.of(-0.21,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of(-0.10,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.041, concentric.projectToBoundary(S2Point.of( 0.01,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.049, concentric.projectToBoundary(S2Point.of( 0.16,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.038, concentric.projectToBoundary(S2Point.of( 0.29,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.097, concentric.projectToBoundary(S2Point.of( 0.48,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.022, concentric.projectToBoundary(S2Point.of( 0.64,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.072, concentric.projectToBoundary(S2Point.of( 0.79,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.022, concentric.projectToBoundary(S2Point.of( 0.93,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.091, concentric.projectToBoundary(S2Point.of( 1.08,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.037, concentric.projectToBoundary(S2Point.of( 1.28,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.051, concentric.projectToBoundary(S2Point.of( 1.40,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.041, concentric.projectToBoundary(S2Point.of( 1.55,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of( 1.67,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.044, concentric.projectToBoundary(S2Point.of( 1.79,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.201, concentric.projectToBoundary(S2Point.of( 2.16,  phi)).getOffset(), 0.01);
 
     }
 
@@ -544,7 +544,7 @@ private SphericalPolygonsSet buildSimpleZone(double[][] points) {
     }
 
     private S2Point s2Point(double latitude, double longitude) {
-        return new S2Point(Math.toRadians(longitude), Math.toRadians(90.0 - latitude));
+        return S2Point.of(Math.toRadians(longitude), Math.toRadians(90.0 - latitude));
     }
 
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubCircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubCircleTest.java
index 6cd5618..69a3950 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubCircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubCircleTest.java
@@ -110,9 +110,9 @@ public void testSPlit() {
     public void testSideSplitConsistency() {
 
         double tolerance = 1.0e-6;
-        Circle hyperplane = new Circle(new Vector3D(9.738804529764676E-5, -0.6772824575010357, -0.7357230887208355),
+        Circle hyperplane = new Circle(Vector3D.of(9.738804529764676E-5, -0.6772824575010357, -0.7357230887208355),
                                        tolerance);
-        SubCircle sub = new SubCircle(new Circle(new Vector3D(2.1793884139073498E-4, 0.9790647032675541, -0.20354915700704285),
+        SubCircle sub = new SubCircle(new Circle(Vector3D.of(2.1793884139073498E-4, 0.9790647032675541, -0.20354915700704285),
                                                  tolerance),
                                       new ArcsSet(4.7121441684170700, 4.7125386635004760, tolerance));
         SplitSubHyperplane<S2Point> split = sub.split(hyperplane);


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org